Nützliche JVM Flags – Teil 1

4 Kommentare

Moderne Java Virtual Machines (JVMs) besitzen ein beeindruckendes Spektrum von Fähigkeiten, um Java-Anwendungen effizient und stabil auszuführen: Adaptive Speicherverwaltung, Garbage Collection, JIT-Compiler, dynamisches Classloading, Lock-Optimierung und vieles mehr. Zur Laufzeit der Anwendung optimiert die JVM basierend auf Messungen das Zusammenspiel ihrer Algorithmen – und das vollkommen transparent für Entwickler und Benutzer. Der über die Jahre hinweg erzielte Fortschritt in der JVM-Technologie spielt nicht nur eine wesentliche Rolle beim anhaltenden Erfolg von Java, er hat auch den Weg für eine Reihe weiterer Programmiersprachen bereitet, die JVM-kompatiblen Bytecode generieren und so die Vorteile der JVM für sich nutzen.

Bei so viel Automatik sind Mechanismen zum Monitoring und Performance-Tuning von Anwendungen sehr wichtig. Im Fehlerfall oder bei ungenügender Performance soll die Ursache schnell und sicher ermittelbar und idealerweise durch gezieltes Tuning korrigierbar sein. Auch hier glänzt die JVM mit einer Fülle von Konfigurationsmöglichkeiten und Tools. Besonders interessant für den Bereich Performance-Tuning sind die Kommandozeilen-Flags, die man beim Start der JVM setzen kann. Es gibt allerdings mehrere Hundert solcher Flags, so dass man leicht den Überblick verlieren kann. In dieser Blog-Serie werden wir die besonders relevanten Flags aus der Masse extrahieren, diese näher kennenlernen und ihre Einsatzgebiete verstehen.

Bevor es losgeht, noch ein paar Worte zur Organisation: Wir werden uns sowohl mit einfachen Standard-Flags der Kategorie „Sollte jeder kennen“ als auch mit ungewöhnlichen Flags (die man zwar selten einsetzt, aber von denen man viel über die JVM lernen kann) beschäftigen. Themenverwandte Flags werde ich in einem Eintrag bündeln, wann immer ich das für sinnvoll halte. Die Darstellung bezieht sich ausschließlich auf die HotSpot JVM des Sun/Oracle JDK 6. Andere JVMs bieten aber oft vergleichbare Flags an, ggf. mit anderen Namen, so dass man hier indirekt auch über diese etwas lernen kann.

-server und -client

Es gibt zwei Typen der HotSpot-JVM, nämlich „Server“ und „Client“. Die Server-VM verwendet großzügigere Einstellungen für den Heap, nutzt per Default einen parallelen Garbage Collector und führt zur Laufzeit aggressivere Code-Optimierung durch. Die Client-VM verhält sich konservativer und reduziert dadurch Startup-Zeit und Memory-Footprint. Für die Auswahl der JVM werden Rechner anhand bestimmter Kriterien bzgl. ihrer Hardware und ihres Betriebssystems nach Client- und Server-Klasse unterschieden. Auf einem Rechner der jeweiligen Klasse wird automatisch die entsprechende JVM gestartet. Die aktuellen Kriterien lassen sich hier nachlesen.

Möchte man selbst bestimmen welche JVM verwendet wird, so kann man dazu die Flags -server bzw. -client nutzen. Obwohl die Server-VM ursprünglich vor allem auf die Verwendung für lang laufende Server-Prozesse ausgerichtet war, zeigt sie heutzutage auch in vielen Standalone-Anwendungen teilweise deutlich bessere Performance als die Client-VM. Meine Empfehlung ist, die Server-VM immer dann zu verwenden, wenn Performance im Sinne kurzer Laufzeit bzw. schnellstmöglicher Verarbeitung wichtig ist. Dies stellt man durch das Setzen des -server Flags sicher. Zu beachten ist, dass man ggf. ein JDK (und nicht nur ein JRE) installieren muss, um die Server-VM verwenden zu können.

-version und -showversion

Wie können wir herausfinden, welche JVM verwendet wird wenn wir java aufrufen? Wenn sich mehrere Java-Installationen auf einem System befinden (besonders gefährlich sind hier vorinstallierte JVMs), dann besteht immer ein gewisses Risiko, aus Versehen die falsche JVM zu starten. An dieser Stelle hilft das Flag -version, das Informationen über die verwendete JVM ausgibt und dem Benutzer damit viel Ärger ersparen kann. Ein Beispiel zur Verwendung des Flags:

$ java -version
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Client VM (build 19.1-b02, mixed mode, sharing)

Wir sehen hier die Versionsnummer (1.6.0_24) und die exakte Build-ID des verwendeten JRE (1.6.0_24-b07). Außerdem sehen wir den Namen (HotSpot), den Typ (Client) und die Build-ID (19.1-b02) der verwendeten JVM. Zusätzlich erhalten wir noch Informationen über das Verhalten der JVM. Sie verwendet den Mixed-Mode („mixed mode“), d.h. sie übersetzt Bytecode zur Laufzeit dynamisch in Maschinencode. Dies ist das Standardverhalten von HotSpot. Ebenso erfahren wir, dass Class Data Sharing („sharing“) aktiviert ist, d.h. die System-Klassen des JRE befinden sich in einem Read-Only-Cache (.jsa-Datei, „Java Shared Archive“), der beim Classloading von mehreren JVM-Prozessen gemeinsam genutzt wird. Dies kann die Performance gegenüber einem ständigen erneuten Einlesen und Verifizieren der Klassen aus jar-Archiven verbessern. Zu beachten ist, dass Class Data Sharing nur von der Client-VM unterstützt wird.

Ein potentieller Nachteil bei der Verwendung von -version ist, dass die JVM sich unmittelbar nach der Ausgabe beendet, die eigentliche Anwendung also nicht ausgeführt wird. Hier schafft das Flag -showversion Abhilfe, welches die gleichen Ausgaben wie -version liefert und im Anschluss auch die eigentliche Anwendung ausführt. Mit -showversion kann man also prima Informationen über die verwendete JVM loggen, die sich zu einem späteren Zeitpunkt als nützlich erweisen können.

-Xint, -Xcomp und -Xmixed

Wir haben den Mixed-Mode der JVM oben bereits kurz kennengelernt. Nicht unbedingt für den Alltagsgebrauch bestimmt, aber doch sehr interessant für Experimente sind die Flags -Xint und -Xcomp. Mit -Xint zwingt man die JVM, sämtlichen Bytecode ausschließlich interpretiert auszuführen, was zu einer deutlichen Verlangsamung führt (in der Regel um ca. Faktor 10). Mit -Xcomp hingegen kann man das exakt umgekehrte Verhalten erreichen, d.h. sämtlicher Bytecode wird sofort mit maximaler Optimierungsstufe kompiliert. Das hört sich zunächst einmal sehr gut an, denn so kann man den langsamen Interpreter komplett umgehen. Dennoch werden viele Anwendungen durch die Verwendung von -Xcomp ebenfalls verlangsamt werden, wenn auch nicht so stark wie im Fall von -Xint. Der Grund dafür ist, dass wir der JVM mit -Xcomp die Möglichkeit nehmen, ihren JIT-Compiler effektiv einzusetzen. Der JIT-Compiler erstellt zunächst Nutzungsprofile einzelner Methoden und optimiert deren Code dann gezielt (und teilweise spekulativ) auf das tatsächliche Verhalten der Anwendung. Auch werden Methoden nur dann kompiliert, wenn sie sich dessen tatsächlich als würdig erweisen, d.h. intensiv genug genutzt werden um einen Hotspot darzustellen.

Betrachten wir zur Illustration die Ergebnisse eines einfachen Beispiel-Benchmarks, bei dem eine HashMap mit Objekten gefüllt wird und diese anschließend wieder ausgelesen werden. Das berechnete Ergebnis ist der Mittelwert der Ausführungszeiten einer großen Anzahl von Läufen des Benchmarks.

$ java -server -showversion Benchmark
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, mixed mode)
 
Average time: 0,856449 seconds
$ java -server -showversion -Xcomp Benchmark
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, compiled mode)
 
Average time: 0,950892 seconds
$ java -server -showversion -Xint Benchmark
java version "1.6.0_24"
Java(TM) SE Runtime Environment (build 1.6.0_24-b07)
Java HotSpot(TM) Server VM (build 19.1-b02, interpreted mode)
 
Average time: 7,622285 seconds

Natürlich finden sich auch Benchmarks, bei denen -Xcomp vorteilhaft ist. Gerade bei lang laufenden Anwendungen ist es aber wenig ratsam, auf die Finessen des JIT-Compilers und das dynamische Optimierungspotential der JVM zu verzichten, denn die Performance-Unterschiede können hier wesentlich drastischer ausfallen als bei dem oben gezeigten Benchmark. Abschließend sei noch erwähnt, dass Mixed-Mode auch sein eigenes Flag besitzt, -Xmixed, welches aber standardmäßig aktiviert ist und deshalb nicht gesetzt werden muss.

Schlusswort

Es gibt bestimmt noch mehr über die vorgestellten JVM Flags zu sagen, deshalb ergänzt ruhig in den Kommentaren alles was ihr für wichtig haltet oder beschreibt wie ihr die Flags gewinnbringend einsetzen konntet. Und wenn ihr Interesse an bestimmten Flags habt die ich in einem der folgenden Beiträge vorstellen soll, schreibt doch einfach einen Vorschlag und ich werde sehen was ich tun kann.

Patrick Peschlow

Dr. Patrick Peschlow ist Entwicklungsleiter bei der CenterDevice GmbH und verantwortlich für die Architektur sowie die technische Umsetzung der Anwendung. Es gibt Leute, die sagen, DevOps sei sein zweiter Vorname.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • Mirko Novakovic

    8. März 2011 von Mirko Novakovic

    Super Artikel! -Xint und -Xcomp kannte ich noch nicht…

  • TJ

    Little lost – I thought Java compiler converts source code into bytecode which gets executed by JVM at runtime.
    So there is only byteCode available to JVM.What do you mean by -Xint only interpreted?
    Are you implying times JVM tries to understand the bytecode directly without JIT compiler kicking in?
    please elabrate?

    • Patrick Peschlow

      Hi TJ,

      yeah, you are on the right track. The input to the JVM is bytecode, and then there are several choices how it handles the bytecode during program execution. At times the JVM interprets bytecode only, without compiling it into native code first. In fact, every Java program you run will normally have some fraction of interpreted bytecode during its execution.

      Early JVMs only contained an interpreter, so your whole Java program bytecode was interpreted only. That’s the main reason why Java was (rightly) considered slow in its early years. Today, modern JVMs still allow you to use interpreted-only mode by specifying -Xint on the command line. Just add -XX:+PrintCompilation to the command line in addition to -Xint and you can see yourself that no compilation of bytecode into native code will happen. Compare the output with a run of the same Java program without -Xint.

      When the JVM started to support bytecode-to-native compilation, people realized that it does not make sense to blindly compile each and every method into native code. Instead, the concepts/technologies nowadays known as „Just-in-time-compilation“ and „HotSpot“ were developed. In a nutshell, after startup the JVM first of all interprets the bytecode and then during program execution decides which methods to compile into native code. The idea is that only „hot“ methods are worth the compiling/optimization effort required to produce efficient native code. On the contrary, „cold“ methods will be handled in interpreted mode until they become „hot“ – which might never happen for some methods.

      By the way, when you dynamically reload classes or instrument methods at run time, the new bytecode will usually also be interpreted for a while even if the old version was already compiled. So, for every new piece of bytecode it sees, the JVM normally takes a while to decide whether it is „hot“.

      This means that you will indeed find bytecode interpretation with every modern JVM execution. If you would like every method to be compiled into native code the first time it is called, you could specify -Xcomp on the command line, but I cannot really recommed this approach. JVMs are pretty clever nowadays!

      Thanks for your comment. I should really consider publishing an English translation of the series 🙂

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.