Feintuning der Speicherbelegung von Java-Programmen mit visualgc

Nicht selten hängen Kernprozesse eines Unternehmens von der Verfügbarkeit von Business-Applikationen ab. Ein Java-Entwickler, der Wert auf Tempo und Langlebigkeit eines Prozesses legt, tut gut daran, ihm mit visualgc auf den Zahn zu fühlen.

Lesezeit: 13 Min.
In Pocket speichern
vorlesen Druckansicht
Von
  • Christian Pemsl
  • Michael Renner
  • Alexander Neumann
Inhaltsverzeichnis

Java erfreut sich bei Business-Anwendungen großer Beliebtheit. Nicht selten hängen Kernprozesse eines Unternehmens von der Verfügbarkeit solcher Applikationen ab. Ein Entwickler, der Wert auf Tempo und Langlebigkeit eines Prozesses legt, tut gut daran, ihm mit visualgc auf den Zahn zu fühlen.

Java steht im Ruf, zwar mächtig und erweiterbar zu sein, doch unterliegt jedes in Java geschriebene Programm dem Generalverdacht der mangelnden Geschwindigkeit. Während mit frühen Java-Versionen programmierte Anwendungen tatsächlich deutlich langsamer liefen als beispielsweise eine in C oder C++ geschriebene Applikation, hat Sun spätestens mit Java 5 die Performance deutlich erhöht. Heute ist, von ungeschicktem Programmcode abgesehen, die Ursache langsamer Java-Programme meist darin zu suchen, dass die unzähligen Parameter zur Speicherbenutzung und Garbage Collection nicht optimal gewählt wurden.

Anders als bei anderen Hochsprachen müssen sich Java-Programmierer nicht um Speicherreservierung und -freigabe kümmern. Das übernimmt die Virtual Machine (VM). Somit stellt sich dem Entwickler die Aufgabe, sie hinsichtlich der Speichernutzung zu optimieren und die Freigabe nicht länger benötigter Objekte zu erreichen.

Die Voreinstellungen der Virtual Machine ermöglichen kaum mehr als grundsätzliches Funktionieren. Beim Start der Anwendung angegebene Parameter verbessern die Laufzeit. Am bekanntesten sind sicherlich Xms für den Anfangswert des Heap-Speichers sowie Xmx, das seine Maximalgröße festlegt. Weitere Parameter bestimmen, wie die VM mit Speicher umgeht und die Funktionsweise der Garbage Collection (GC), also die Freigabe nicht mehr benötigten Speichers. Die wichtigsten Optionen führt die folgende Tabelle auf.

Optionen zur Speicherverwaltung
Option Erklärung
-server JIT- und HotSpot-Optimierung für Server-Anwendungen JIT- und HotSpot-Optimierung für Client-Anwendungen
-client
-Xms=Xm Anfangsgröße des Heap (Young Generation + Old Generation) Maximale Größe (Anfangs- + Virtual-Bereich) des Heap
-Xmx=Xg
-XX:NewSize= Anfangsgröße der Young Generation (Eden + Survivor Space 1 + Survivor Space 2)
-XX:MaxNewSize= Maximale Größe der Young Generation
-XX:SurvivorRatio= Verhältnis Eden/Summe: hoher Wert -> Survivor wird kleiner; kleiner Wert -> Eden befördert Objekte direkt in die Old Generation
-XX:PermSize= Anfangsgröße der Permanent Generation
-XX:MaxPermSize= Maximale Größe der Permanent Generation (Anfangs- + Virtual-Bereich)
-XX:MinHeapFreeRatio=X Heap wird erweitert, sobald weniger als X Prozent freier Speicher zur Verfügung steht.
-XX:MaxHeapFreeRatio=X Heap wird verkleinert, sobald mehr als X Prozent freier Speicher zur Verfügung steht.
-XX:+UseParallelGC Verwendung der parallelen Garbage Collection
-XX:+UseParNewGC Verwendung der neuen Methode zur parallelen Garbage Collection
-XX:+UseParallelGCThreads= Anzahl der GC Threads bei den beiden parallelen GCs
-XX:+UseConcMarkSweepGC Verwendung der Concurrent mark-sweep (CMS) Garbage Collection
-XX:CMSInitiatingOccupancyFraction=X CMS GC startet bei X Prozent (Default-Wert 68 %)
-XX:MaxTenuringThreshold= Anzahl der GC-Läufe, die ein Object in der Young Generation verbleiben darf.
-XX:MaxGCPauseMillis= Max. Dauer, in der die Anwendung zur GC zu stoppen ist (default ist unendlich). Bei Angabe von 0 lassen sich die Objekte direkt in die Old Generation befördern.

Zusammensetzung der Speicherbereiche (Abb. 1)

Die Zusammensetzung der einzelnen Speicherbereiche eines Java-Prozesses zeigt Abbildung 1. Eine 32-Bit-JVM kann maximal 4 GByte belegen, das umfasst den Heap einschließlich der Bereiche des Perm- und Virtual-Speichers.

Bei aufwendigen Applikationen lässt sich der Ressourcenbedarf meist nicht abschätzen. Es hilft jedoch nicht, der VM möglichst viel Speicher zuzuteilen, da sie beispielsweise während der GC viel Zeit für die Bereinigung nicht benötigter Bereiche verschwendet. Deshalb muss man durch eine Analyse die optimalen Betriebsparameter für ein gegebenes Programm ermitteln.

Java nimmt dem Entwickler mit der eingebauten Garbage Collection viel Arbeit ab. Fehler durch eine verfrühte oder vergessene Freigabe nicht mehr benötigter Objekte gehören der Vergangenheit an. Das zieht allerdings Nachteile nach sich. So ist kaum zu beeinflussen, wann das System die GC durchführt. Harte Echtzeitanforderungen sind deswegen mit Java nur selten zu erfüllen. Abhängig vom eingesetzten GC-Algorithmus lässt sich eine Programmunterbrechung durch eine parallele GC zumindest in Grenzen verhindern.

Java hat im Lauf der Zeit mehrere Verfahren für die GC benutzt. Zwar sind neuere Umsetzungen den älteren meist überlegen, in manchen Fällen mag es jedoch lohnen, einem der älteren Algorithmen den Vorzug zu geben. Informationen über die verfügbaren Verfahren liefert Suns GC-FAQ. In der Praxis führt das schnelle Experimentieren mit den drei Parametern -XX:+UseParallelGC, -XX:+UseParNewGC und -XX:+UseConcMarkSweepGC ebenso zum Erfolg. Informationen darüber, wann eine CG läuft und wie lange sie dauert, liefert der Schalter -verbose:gc beim Programmaufruf. Die Option ist unbedingt notwendig, wenn man Logging-Optionen zur GC angibt.