Das Java-Problem

Viel ist schon über Java geschrieben worden. Über die indonesischen Sehenswürdigkeiten, die Vorteile der Sprache und die Nachteile dieser Sprache. Häufig liest man jedoch über die angebliche Langsamkeit Javas. Wie so oft stimmt daran nur die Hälfte. Dieser Text soll etwas Aufklärung mit meiner Sicht der Dinge bringen.

Das Konzept Java

Die Idee mit dem "Interpreter Java" stößt vielen sauer auf, ist aber nur Compilerbau konsequent weitergedacht. Klassischerweise ist ein Compiler wie folgt aufgebaut: Frontend-Optimierer-Backend. Das Frontend ist der Teil, der die eigentliche Sprache versteht. Es baut aus dem Quelltext einen Syntaxbaum und daraus eine Compilerinterne Zwischensprache. Für solche Zwischensprachen hat es sich bewährt Stack-basierte Sprachen zu verwenden. Sie eignen sich recht gut zur Optimierung. Die Mitte bildet der Optimierer. Er ist das Juwel eines Compilers. Das Backend schließlich hat zur Aufgabe, plattformspezifischen Kode zu generieren. Also z.B. x86- oder PPC-Maschinenbefehle. Auch hier steckt eine Menge Wissen aber auch Optimierungspotential drin.
Die Idee bei einer Plattformunabhängigen Sprache ist nun, den Compiler aufzutrennen und nur den optimierten Zwischenkode von sich zu geben. Das Backend macht dann die letzten Schritte beim Benutzer und übersetzt den Zwischenkode vorort und bei Bedarf in plattformspezifischen Kode. Wenn es jedoch kein wirkliches Backend für diese Plafform gibt, kann alternativ auch ein Zwischenkodeinterpreter diese Aufgabe übernehmen.
Auf den wichtigen Plattformen existieren natürlich sog. Just In Time (JIT)-Compiler oder sog. Hotspotcompiler. Dadurch wird der Java-Zwischenkode dort nicht interpretiert sondern mindestens auf weiten Strecken in Form von plattformspezifischem Kode ausgeführt. Dennoch hat der "Notfallinterpreter" sich in den Köpfen der Leute durchgesetzt.

Geschwindigkeit von Java

Wie im vorangegangenen Absatz erklärt, läuft ein Javaprogramm in der Regel als native-Programm welches durch einen JIT-Compiler erzeugt wurde. Dementsprechend ist Java bei Schleifen und numerischen Berechnungen auch genau so schnell wie ein plattformspezifisches Programm. Siehe dazu c't 19/03, Seite 204 und c't 21/03, Seite 222.
Tatsache ist aber auch, dass in Java geschriebene Programme, vor allem die mit SWING, fürchterliche Langweiler sind und Arbeitsspeicher in rauen Mengen verschlingen. Als Paradebeispiele seien hier die monströsen Entwicklungswerkzeuge genannt: JBuilder, Posseidon, ArcStyler, Together und auch Eclipse. Woran ligt es also, dass trotz JIT-Compiler die Programme so lahmen und vor allem so ausladend sind?

SWING ist an allem schuld

Nicht ganz. die Kombination macht es. Ich sehe die Schuld in einr Kombination aus SWING/Bibliotheken, Heapmanagement, Klassenrepräsentation im Speicher, Sprachkonzept (Einfachvererbung und ausschließliche Referenzsemantik). Die Sprache Java kennt weder das Konzept Mehrfachvererbung noch eine Wertesemantik von Objekten. Mit letzterem meine ich das Erstellen von Obejten auf dem Stack bzw. innerhalb eines anderen Objekts wie es in C++ beispielsweise möglich ist. Java kennt eben nur Referezsemantik was impliziert, dass Objekte nur auf dem Heap existieren.
Einen großteil der Schuld tärgt das GUI-Framework SWING. SWING ist sehr flexibel, anpassbar und mustergültig objektorientiert. Das kostet jedoch Ressourcen. Um z.B. jeden Menüeintrag in einer eigenen Farbe darzustellen sind viele Objekte und noch mehr Referenzen nötig. Jedes Kontrollelement besteht aus mindestens drei Objekten (MVC-Ansatz). Dazu kommen temporäre Objekte und diverse anonyme Listenerobjekte. So kommt man beispielsweise für einen einfachen Button auf mindestens 18 Objekte. Die sind zwar an sich meist recht klein, müssen aber erstellt werden, haben eine unbestimmte Lebensdauer und bringen darüber hinaus alle den (speicher-)Overhead eines Javaobjekts mit. Das belastet natürlich den Heap und er fragmentiert. Da die Objekte nicht zur frühestmöglichen Zeit freigegeben werden, sondern irgendwann, wächst der Heap auch noch. Der Heap wird dadurch löchrig. Er benötigt viel Platz wobei ca. die Hälfte frei ist. Die darunter liegenden Speicher-Seiten (Paging des BS) können aber nicht freigegeben werden, da sie alle irgendwie Daten enthalten. Eine Gegenmaßnahme ist eine andere Allozierungsstrategie des Heaps. Dieses Problem wurde u. a. mit Java 1.5 adressiert. Dort kann man mittels Aufrufparameter zwischen verschiedenen Allozierungsstratigien wählen. Eine davon ordnet z.B. kleine Objekte hinten und große Objekte vorne an. Diese Strategie nutzt dabei aus, das große Objekte tendenziel länger leben und die Fragmentierung so verringert wird. Eine wirkliche Lösung stellt allerdings auch diese Strategie nicht dar. Diese läge vielmehr darin, ggf. Mehrfachvererbung zuzulassen aber vor allem statische Objektschachtelung zuzulassen. Eben so, wie das von C++ auch bekannt ist. Dadurch fielen einerseits viele Heapoperationen weg und andererseits sind mit Mehrfachvererbung auch nicht so wilde Verrenkungen über die Erbhierarchie nötig. Die Zwischensprache von Java gibt das her, doch ist das nicht mit dem Konzept Java vereinbar. Es würde Änderungen im Objektmodell und ein Paradigmenwechsel notwendig. Daher werden weiterhin während eines Programmlaufs eher mehr als weniger Objekte erstellt, der Heap fragmentiert und vergrößert sich, der Systemspeicher wird knapp, Teile des weit verstreuten Workingsets werden vom Betriebsystem ausgelagert und die Anwendung erlahmt.

Das Java-Konzept ist schuld

Abschließend sei nochmals darauf hingewiesen, dass es keineswegs die Grafikausgabe von SWING ist, die darauf basierende Anwendungen lahmen lässt. Die Grafik ist natürlich etwas langsamer als diejenige von z.B. C-Programmen, aber das ist bei heutigen Prozessorleistungen vernachlässigbar. Der eigentliche Grund ist das Konzept und Objektmodell von Java (auch die fehlende Mehrfachvererbung). Die dadurch verursachte Ojektinflation und einhergehend das Fragmentieren und Wachsen des Heaps. Wenn dann teile des Workingsets ausgelagert werden, dann lahmt eine Anwendung. Davon ist im Übrigen auch das geschwindigkeitsmäßig hoch gelobte Eclipse betroffen. Allerdings wirkt sich das dort weniger stark aus, da das zugrunde liegende Toolkit SWT geradliniger oder effizienter entworfen ist als Swing. Dort sind für einen Button vielleicht 5 Objekte nötig. In der Folge fragmentiert der Heap natürlich weniger.

© Robert Köpferl