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