Java Magazin 12/07

Java Performance Tools, Teil 1

Autor:

Die JVM bietet integrierte Technologien und Tools für Java Performance Tuning und Troubleshooting an. Ein tieferes Verständnis der Grundlagen und Benutzung dieser Werkzeuge hilft jedem Java Entwickler bei der Analyse von Performance- und Stabilitätsproblemen.

Die Analyse von Performance- und Stabilitätsproblemen kann sehr zeitaufwändig und kritisch sein. Gerade die Analyse von schwer reproduzierbaren Fehlern in produktiven Umgebungen ist ohne den Einsatz der richtigen Werkzeuge fast unmöglich. Java bietet mit JVMTI und JMX zwei Technologien, die die Grundlage für fast alle kommerziellen Werkzeuge sind. Der erste Teil dieser Artikelserie beschäftigt sich mit diesen Basistechnologien für Performance Tuning und Troubleshooting, sowie den JVM Tools, die für die Auswertung der bereitgestellten Daten, Dumps und Logdateien genutzt werden können.

Java wird immer stärker für unternehmenskritische Anwendungen eingesetzt und die Komplexität dieser Anwendungen nimmt tendenziell zu. Die Zentralisierung der Anwendungen auf Basis von Java EE und der Einsatz von Application Servern bedeuten, im Vergleich zu früheren Client-Server Anwendungen, eine erhöhte Parallelität und Belastung der Server. Gestiegene Anforderungen an Performance, Skalierbarkeit und Verfügbarkeit führen unweigerlich zu verteilten Systemarchitekturen mit Clustern von Webservern, Applicationservern und Datenbanken.

Die Verteilung dieser Anwendungen bedingt auch eine Replikation von Statusinformationen und gesteigerte Netzwerklast durch entfernte Service Aufrufe. Mit serviceorientierten Architekturen (SOA) nimmt der Trend zur Verteilung von Anwendungen stärker zu, wobei meistens auch die Anforderungen an Performance und Verfügbarkeit der zentral genutzten Services an Bedeutung gewinnt. In diesen Umgebungen ist eine Analyse von Performance-Engpässen und Stabilitätsproblemen extrem schwierig, da sehr viele Komponenten bei einer fachlichen Transaktion beteiligt sind. Die häufigsten technischen Probleme komplexer Java Anwendungen sind dabei:

  • Schlechte Performance
  • Memory Leaks und OutOfMemoryError
  • Garbage Collection Overhead
  • Deadlocks und Loops
  • Synchronisations- und Threadsafety- Probleme

Die Auswirkungen dieser Probleme sind vielfältig und reichen von schlechten Antwortzeiten, einer geringen Anzahl paralleler Benutzer, bis hin zu einem Ausfall des gesamten Systems und korrupten Daten. Dies führt bei geschäftskritischen, produktiven Systemen unweigerlich zu einer Eskalation im Management, an deren Ende leider immer die Entwickler stehen. Die Aufgabe ist klar und einfach formuliert: Die Probleme schnell identifizieren und noch schneller beseitigen. Aber wie?

Tools für die Analyse

Es gibt unterschiedliche Tools, um die beschriebenen Probleme zu analysieren. Eine der entscheidenden Fragen ist, wie viele Informationen man für die Lösungsfindung benötigt und unter welchen Bedingungen man eine Analyse durchführen kann. Eine Faustregel ist: Je mehr Daten von einem Tool gesammelt werden, desto höher ist der Einfluss des Tools auf die Anwendung selbst. Es gibt zum Beispiel kein Tool, das einen kompletten Memory Dump inklusive Referenzen erzeugen kann, ohne das System de facto zum Stillstand zu bringen. Aus diesem Grund unterscheidet man drei Arten von Tools:

  • Monitoring-Tools eignen sich für den Einsatz in produktiven Umgebungen. Diese Tools haben einen akzeptablen Overhead und liefern Informationen über Probleme in laufenden Anwendungen bis auf Komponentenlevel. Mithilfe von Trendanalysen und längerfristigen Betrachtungen, können Probleme proaktiv vermieden werden.
  • Diagnose-Tools sind für den Einsatz in Integrations- und Testumgebungen geeignet. Diese Tools können auch unter erzeugter Last detaillierte Analysedaten bis auf Methodenlevel liefern und haben noch einen akzeptablen Overhead. Gerade für Probleme, die nur unter erhöhter Belastung auftreten und nicht in der Entwicklungsumgebung, sind diese Tools unverzichtbar.
  • Profiler und Memory Debugger sind für den Einsatz in der Entwicklungsumgebung ausgelegt und können Performance- Informationen bis auf Zeilenebene liefern. Mithilfe von Memory Debuggern können zudem Memory Leaks analysiert werden. Der Overhead, gerade bei der Memory-Analyse, ist sehr hoch und man kann diese Tools nicht in produktiven Umgebungen einsetzen.

Im Folgenden werden die Technologien beschrieben, auf denen diese Tools basieren. Des Weiteren werden die integrierten Tools in der JVM und dem JDK vorgestellt und anhand von Problemszenarien beschrieben.

JVM Tooling Interface

Die Java Virtual Machine verfügt seit der Version 1.2 über eine Schnittstelle für Profiling Tools, das Java Virtual Machine Profiling Interface (JVMPI). Die Schnittstelle definiert die Funktionen für einen in C oder C++ geschriebenen Agenten, der sich für die wichtigsten JVM Events registrieren kann. Zu den Events, für die sich ein Agent registrieren kann, zählen:

  • JVM Life Cycle Events: Für die Initialisierungsphase der JVM
  • Class Life Cycle Events: Load und Unload einer Klasse. Der Profiling Agent hat zusätzlich die Möglichkeit, den Inhalt der Klasse zu lesen und zu verändern
  • Thread Life Cycle Events: Für das Erzeugen und Zerstören von Threads
  • Objekt Life Cycle Events: Bei Allokation, Löschen und Verschieben von Objekten auf dem Heap durch den Garbage Collector
  • Method Call Events: Entry und Exit jeder aufgerufenen Methode
  • Monitor Event – Anfrage, Enter, Exit und Wait auf einen Java-Monitor (synchronized und Object.wait())
  • GC Events: Beim Start und Ende der GC-Phase.

Neben diesen Events besteht die Möglichkeit, über den Agenten einen vollständigen Heapdump zu erzeugen, das heißt, alle Objekte auf dem Heap inklusive der Referenzen in eine Datei zu schreiben. JVMPI ist allerdings kein Standard und muss somit nicht zwingend von allen JVM-Herstellern unterstützt werden bzw. kann in der Implementierung abweichen. Mit JSR 163 (Java Platform Profiling Architecture) wurde ein Standard entwickelt, der in Java 5 als Java Virtual Maschine Tool Interface (JVMTI) eingegangen ist. JVMTI basiert auf dem Agenten-Konzept von JVMPI, sodass auch hier ein C- oder C++-Agent entwickelt werden muss, der sich dann für bestimmte Events registrieren und Funktionen der JVM steuern kann. Eine weitere, sehr interessante Erweiterung von JVMTI ist, dass der Inhalt einer Klasse nicht mehr nur zur Ladezeit verändert werden kann, sondern auch dynamisch zu einem späteren Zeitpunkt des Lebenszyklus einer Klasse.

Die Events von JVMPI und JVMTI liefern die benötigten Daten, um Performance, Speicher, Threading- und Garbage- Collection-Probleme im Detail zu analysieren. Das ist auch der Grund, warum Java Profiling und Memory Debugger fast ausschließlich auf Basis dieses Agentenkonzepts funktionieren. Der hohe Detailgrad hat aber seinen Preis und resultiert in einem Performance Overhead, der zu groß ist, um diese Tools in Produktion oder Lasttestumgebungen in vollem Umfang einzusetzen. Monitoring und Diagnose- Tools, die mit geringem Overhead arbeiten, nutzen daher in der Regel eine Form der Bytecode-Instrumentierung für die Erfassung von Laufzeitdaten.

Bytecode-Instrumentierung

Mit Bytecode-Instrumentierung werden die Techniken zur nachträglichen Veränderung des compilierten Bytecodes einer Java-Anwendung bezeichnet. Performance Tools nutzen diese Technik, um den eigentlichen Messcode in den bestehenden Applikationscode einzufügen. Mithilfe von Libraries wie der Apache Byte Code Engineering Library (BCEL) [1] wird der bestehende Bytecode analysiert und dann ein neuer Bytecode hinzugefügt oder ein bestehender Code verändert. Der Zeitpunkt der Manipulation ist allerdings je nach verwendeter Technologie unterschiedlich. Bei der statischen Bytecode-Instrumentierung wird der Bytecode vor dem Laden der Klassen mit einem Preinstrumentor instrumentiert. Der Bytecode läuft daher auf jeder JVM und es werden keine zusätzlichen Anpassungen oder Libraries zur Laufzeit benötigt. Nachteilig ist, dass ein manueller Schritt für die Instrumentierung notwendig ist und Systemklassen des Runtime Environment nicht instrumentiert werden können.

Bei der Load-Time-Bytecode-Instrumentierung wird ein eigener Class- Loader verwendet, der beim Laden einer Klasse den entsprechenden Bytecode einfügt. Alternativ kann auch ein JVMPI-oder JVMTI-Agent verwendet werden, um beim entsprechenden Classloading Event den Inhalt der Klasse zu verändern. Man spart also im Gegensatz zum statischen Ansatz einen manuellen Schritt und kann auch Systemklassen instrumentieren.

Dynamische Bytecode-Instrumentierung erlaubt es, den Bytecode einer Klasse auch nachdem sie bereits geladen ist, zu verändern. Diese Möglichkeit existiert erst seit Java 5 und wird daher erst von sehr wenigen Tools unterstützt. Das dynamische Instrumentieren von Klassen wird durch die RedefineClasses-Funktion in JVMTI möglich. Neben JVMTI bietet Java 5 aber auch eine reine Java-Implementierung von Agenten für die dynamische Instrumentierung von Bytecode, die sich im Paket java.lang.instrument befindet. Eigene Agenten zu schreiben, ist relativ einfach, aber nicht nur für Performance- Messungen sinnvoll, deshalb soll nachfolgend beispielhaft die Implementierung eines Agenten in Java beschrieben werden.

Das Package java.lang.instrument bietet Agenten die Möglichkeit, den Bytecode von Klassen dynamisch zu verändern. Die Manipulation des Bytecodes kann auf zwei Wegen geschehen. Zum einen zur Ladezeit durch eine Implementierung des Interfaces ClassFile- Transformer. Zum anderen zur Laufzeit, das heißt, auch nachdem Klassen bereits durch einen ClassLoader geladen wurden. Letzteres geschieht durch die Methode redefineClasses(), welche im Interface Instrumentation definiert ist. Zur Veranschaulichung soll ein einfacher Agent entwickelt werden, der beim Laden jeder Klasse eine Meldung mit dem Namen der Klasse auf der Konsole ausgeben soll.

Die Dokumentation des java.lang.instrument- Pakets gibt eine Übersicht über Anforderungen an einen Agenten und wie dieser eingebunden wird. Um einen eigenen Agenten zu entwickeln, wird eine Bootstrap-Klasse benötigt. Diese muss über eine Methode mit einer Signatur verfügen, die vergleichbar mit der main-Methode für Java-Applikationen ist:

public static void premain(String agentArgs, Instrumentation instrumentation){ … }

Der Methode werden die Parameter für den Agenten übergeben sowie eine Instanz vom Typ java.lang.Instrumentation, die Funktionen zur Instrumentierung von Java-Klassen bietet. Die Veränderung des Bytecodes erfolgt entweder über eine eigene ClassFileTransformer-Implementierung für die Manipulation beim Laden oder Redefinition der Klasse oder direkt über eine neue Klassendefinition vom Typ ClassDefinition, wenn eine Klasse geändert werden soll, die bereits geladen ist. Wird der Bytecode einer bereits geladenen Klasse mit der redefineClasses()- Methode überschrieben, hat dies nur Auswirkungen auf zukünftig aufgerufene Instanzen der Klasse. Instanzen der Klasse die sich in einem aktiven Stack Frame befinden, bleiben von der Änderung unberührt. In dem Beispiel wird ein eigener ClassFileTransformer verwendet, um die Namen der geladenen Klassen auf die Konsole zu schreiben und sie dann unverändert weiterzureichen. Die Implementierung des Interface benötigt nur eine Methode:

package de.codecentric.transformer;

import java.lang.instrument.ClassFileTransformer;

import java.lang.instrument.IllegalClassFormatException;

import java.security.ProtectionDomain;

public class LogClassFileTransformer implements

ClassFileTransformer {

public byte[] transform(ClassLoader loader,

String className, Class<?> classBeingRedefined,

ProtectionDomain protectionDomain,

byte[] classfileBuffer)

throws IllegalClassFormatException {

System.out.println(

“Transformiere die Klasse ‘“ + className + “’“);

return null;

}

}

Nachdem der LogClassFileTransformer mit addTransformer() bei der Instrumentation- Instanz registriert wurde, wird bei jeder Definition oder Redefiniton einer Klasse die Methode transform(…) aufgerufen. Eine Klassendefiniton erfolgt über ClassLoader.definineClass() während Klassenredefinitionen über Instrumentation. redefineClasses() erfolgen. Innerhalb der Methode ist es möglich, den Bytecode der Klasse zu verändern. Der Bytecode muss dem Classfile-Format der JVM-Spezifikation [2] entsprechen. An dieser Stelle bieten sich zur vereinfachten Manipulation verschiedene Open-Source-Tools wie Apache BCEL, ObjectWeb ASM [3] oder JBoss Javassist [4] an. In unserem Beispiel wird die Klasse nicht verändert, weshalb in der transform()-Methode null zurückgeliefert wird. Der Code für den Agenten sieht wie folgt aus:

package de.codecentric.instrument;

import java.lang.instrument.Instrumentation;

import de.codecentric.transformer.

LogClassFileTransformer;

public class JMVTIAgent {

public static void premain(String agentArguments,

Instrumentation instrumentation) {

instrumentation.addTransformer(

new LogClassFileTransformer());

}

}

Der Agent kann jetzt mit jeder beliebigen Java-Anwendung zusammen gestartet werden, um die Klassennamen auf der Konsole auszugeben. Hierzu muss der Agenten-Code aber noch in ein JAR gepackt werden, das über eine spezielle MANIFEST. MF-Datei verfügt:

Manifest-Version: 1.0

Premain-Class: de.codecentric.instrument.JMVTIAgent

Mithilfe des Java-Kommandozeilen-Parameters -javaagent:jarpath[=options] kann der Agent und die agentenspezifischen Parameter definiert werden. Das Beispiel zeigt, dass man mit einem java. lang.instrument-Agenten ohne C oder C++ Code schreiben kann, der spezifische Funktionen in bestehende Java-Anwendungen einfügen kann. Viele AOP Frameworks basieren auf dieser Technologie, um Aspekte in die Anwendungen zur Laufzeit „einzuweben“. Für Profiler- und Diagnose-Tools ist vor allem die Plattformunabhängigkeit des Agenten und die Möglichkeit, Klassen dynamisch zu verändern, interessant. Die Technologie ermöglicht zum Beispiel das Entfernen und Hinzufügen von Messpunkten zur Laufzeit.

Java Management Extension (JMX)

Neben den Laufzeitinformationen, die über die Bytecode-Instrumentierung erzeugt werden, sind für das Monitoring von Anwendungen vor allem auch Informationen über die Infrastrukturkomponenten notwendig. Hierzu zählen auf der untersten Ebene Informationen der JVM, wie Heap-Statistiken oder Thread Informationen. Bei Java EE ist aber auch die Auslastung der Application-Server-Ressourcen notwendig, um Performance-Engpässe identifizieren zu können. Beispielsweise können zu klein gewählte Thread- und Datenbank-Connectionpools dazu führen, dass Benutzeranfragen in einer Warteschlange landen und sich die Verarbeitung verzögert. Aber auch Statistiken von Caches aus eigenen Komponenten oder Frameworks wie Hibernate [5] sind von Bedeutung, wenn ein Bottleneck in einer Anwendung gefunden oder Ressourcen- Engpässe in Produktion frühzeitig identifiziert werden sollen. Mit der Java Management Extension (JMX) steht eine ausgereifte Spezifikation zur Verfügung, die eine Überwachung von Applikationen und Services einfach möglich macht.

JMX besteht aus der Java Management Extensions Instrumentation and Agent Specification (JSR 3) und dem Java Management Extensions Remote API (JSR 160). Seit Java 5 ist JMX ein Teil der Java Standard Edition und somit in jeder JVM verfügbar. Die Basis von JMX bilden so genannte MBeans, die eine Java- Ressource (in den meisten Fällen ein Objekt) verwalten und überwachen. Das MBean Design und die Schnittstellen werden durch JSR 3 definiert. Das MBean Design ist flexibel und einfach zu implementieren, wobei existierende Ressourcen statisch oder dynamisch managebar gemacht werden können. MBeans können Attribute enthalten, die zur Laufzeit abgefragt und verändert werden können. Operationen bieten zudem die Möglichkeit, bestimmte Managementfunktionen zu veröffentlichen und aufzurufen.

Des Weiteren kann ein MBean Notifikationen über bestimmte Events empfangen und versenden. Ein Beispiel für eine MBean-Ressource könnte ein Cache sein, der seine Attribute (Anzahl Einträge …) und Operationen (Cache löschen …) über JMX freigibt. Monitoring-Applikationen können auf Basis dieser Informationen die Auslastung des Caches überwachen und bei Bedarf den Cache automatisiert löschen. Ein Java Cache mit dem Namen Cache kann als MBean freigegeben werden, indem es ein bestimmtes Interface implementiert: CacheMBean. Alle Methoden und Attribute Getter/Setter aus diesem Interface werden nach der Registrierung des MBeans am MBeanServer automatisch über JMX freigegeben. Diese Art von MBeans wird Standard MBean genannt.

Java Management Layer

Sind die Sourcen einer Klasse nicht verfügbar, kann auch ein dynamischer Mechanismus verwendet werden, um Objekte als MBeans zu deklarieren. Dabei wird eine Instanz vom Typ MBeanInfo erzeugt, die alle Informationen über das Management Interface enthält – diese Form der MBeans wird Dynamic MBean genannt. Ein Vorteil von Dynamic MBeans ist vor allem die Möglichkeit, Attribute und Operationen zur Laufzeit über JMX freizugeben bzw. die Freigabe wieder aufzuheben. Viele Frameworks erleichtern die Erzeugung und Verwaltung von MBean, so können z.B. Objekte, die über den Springframework IoC Container [6] erzeugt werden, mithilfe des MBeanExporter von Spring deklarativ als JMX MBean registriert werden.

Mit JSR 160 wurde auch der Zugriff auf die MBean standardisiert. Prinzipiell kann über beliebige Protokolle mit den MBeans kommuniziert werden – im Standard enthalten sind ein RMI Connector, ein JMXMP Connector und ein SOAP Connector. Der Zugriff kann über JAAS abgesichert werden, sodass Unbefugte keinen Zugriff auf die MBeans bekommen.

Mithilfe von JMX können Applikationen sehr einfach ein Management Interface anbieten, das von Monitoring- Tools zur Überwachung genutzt werden kann. Ab Java 5 gibt es aber mit java.lang. management auch ein API für das Management der JVM auf Basis von JMX. Das API definiert neun MBeans für die Überwachung und das Management von ClassLoading, Compilation, Memory, Threads, Runtime, Operating System, Garbage Collector, Memory Manager und Memory Pool. Es werden zum Beispiel detaillierte Heap-Informationen über Auslastung und maximale Größe für alle Heap-Bereiche, wie Young-, Tenuredund PermGen Spaces geliefert. Mithilfe dieser Informationen können die Einstellungen des Heaps auf Basis von echten Daten aus der Produktionsumgebung angepasst und optimiert werden. Mit Java 5 wird auch das Tool jconsole mitgeliefert, das den Zugriff auf MBeans ermöglicht und zudem angepasste Sichten auf die JVM-integrierten MBeans bietet. Abbildung 2 zeigt die Darstellung des Heaps in jconsole.

JConsole

Bei den Application-Servern haben die Hersteller lange Zeit eigene Standards zur Überwachung bereitgestellt. JBoss AS und BEA Weblogic haben schon sehr früh JMX als Management- und Monitoring- Schnittstelle genutzt, während z.B. IBM WebSphere auf ein eigenes Framework mit dem Namen PMI gesetzt hat. Mit der Java EE Management Specification (JSR 77) wurde die Überwachung von Application- Servern und Java-EE-Anwendungen standardisiert. Die Spezifikation beinhaltet:

  • Eine einheitliche Definition der MBean- Namen, sodass die Navigation und das Auffinden von MBean stark vereinfacht werden
  • Standardisierte Events für wichtige Ereignisse im Java EE Application Server
  • Den Status für die Überwachung der MBeans
  • Rudimentäre Performance-Daten und Statistiken für die MBeans.

Mithilfe von JSR 77 können Monitoring- Tools beispielsweise Statistiken über Aufrufanzahl, Performance-Daten (min, max, load, processing) und Fehler von Servlets, JSPs und EJBs auslesen. Einfache JMX Monitoring-Tools wie MC4J [7] bieten bereits vorkonfigurierte Dashboards für JSR-77-kompatible Server. Aktuell implementieren alle bekannten Application- Server JSR 77 in den neueren Versionen. JMX und die darauf basierenden Standards bieten eine gute Möglichkeit, die JVM und Application-Server zu überwachen. Zudem kann die eigene Anwendung einfach um Monitoring-Funktionen und -Daten erweitert werden. Viele Software- Komponenten und Frameworks bieten bereits integrierten Support für JMX, z.B. Hibernate oder log4j.

Logdateien und Dumps

Einige Probleme können nicht mit Bytecode- Instrumentierung oder JMX identifiziert und gelöst werden. Vor allem Memory Leaks, Garbage Collection Overhead und Thread Deadlocks fallen in diese Kategorie. Profiler können mithilfe der beschriebenen JVMPI-/JVMTI- Events die benötigten Informationen auslesen, um diese Probleme zu analysieren. Allerdings ist dies in der Praxis häufig nicht möglich, da die Probleme oft nur unter realen Bedingungen in der Produktionsumgebung auftreten. In diesen Fällen ist der Einsatz eines Profilers nicht möglich, da der Overhead zu groß ist.

Die JVM bietet einige Logdateien und Dumps an, um Produktionsprobleme „offline“ zu analysieren. Mithilfe des Heapdumps können Memory Leaks in einer produktiven Anwendung analysiert werden. Die Sun HotSpot JVM ermöglicht Heapdumps mit dem integrierten HPROF Profiler, der die Referenzimplementierung von JVMTI ist. Der HPROFProfiler wird über den mitgelieferten Agenten mit dem Startparameter -agentlib: hprof=heap=dump,format=b aktiviert. Dabei bewirkt der erste Parameter hprof=heap die Aktivierung des Heapdumps und mit dem Parameter format=b wird das Format des Heapdumps auf ein Binärformat gesetzt. Ist dieser Parameter gesetzt, wird automatisch ein Heapdump beim Beenden der JVM erzeugt – dies ist z.B. auch der Fall, wenn sich die JVM aufgrund eines java.lang.OutOfMemory- Error beendet. Manuell kann der Dump auch über Ctrl-\ oder Ctrl-Break in der Konsole erzeugt werden. Ab Java 6 wird in der Sun HotSpot JVM das JVM MBean HotSpotDiagnosticMBean bereitgestellt, über das ein manueller Heapdump ausgeführt werden kann. Ein Heapdump beinhaltet alle Objekte und deren Referenzen auf dem Heap – die Dump Datei kann aufgrund der Datenmenge je nach eingestellter Heap-Größe sehr umfangreich werden. Mithilfe des Tools Hat [8] bzw. des JVM-Kommandozeilentools JHat kann der Heapdump auf Memory Leaks analysiert, sowie Objektgrößen und Objektmengen bestimmt werden. Moderne Profiler wie JProbe [9] oder JProfiler [10] erlauben zudem, die Heapdumps in das interne Format des Profilers zu konvertieren, sodass die komfortablere Oberfläche der Tools zur Analyse genutzt werden kann.

IBM stellt für seine Heapdumps (das Format unter- Abb. 3: HPjtune GC Überblick scheidet sich von dem HPROF-Format) das kostenfreie Analysetool IBM Heap- Analyzer [11] zur Verfügung.

Um Garbage Collection Probleme aufgrund so genannter Cycling Objects oder falscher Heap-Einstellungen zu identifizieren, definiert die JVM-Spezifikation den Parameter –verbose:gc. Die erzeugten Logausgaben geben die Heap-Auslastung vor und nach dem GC sowie die maximale Heap-Größe und die Dauer der GC-Aktivität an. Die Sun HotSpot JVM bietet noch erweiterte Parameter für die Analyse, die mit -XX:+PrintGCDetails aktiviert werden können. Der Parameter bewirkt, dass detailliere Informationen über die Young Generation des Heaps ausgegeben werden. Es gibt noch einige weitere Parameter, um die Analysedaten zu erweitern. Diese können in den HotSpot-JVM-Optionen [12] nachgelesen werden.

Die Analyse der Logdateien hilft, die Parameter für Heap-Größen und GCEinstellungen zu optimieren. Die Auswertung der Loginformationen auf Basis von Textdateien kann sehr zeitaufwändig und fehleranfällig sein. HP bietet mit dem kostenlosen Tool HPjtune [13] eine Möglichkeit, die Analyse zu vereinfachen. Das Tool fasst alle wichtigen Daten der Da tei zusammen und stellt den Verlauf des Heaps und der GC-Aktivitäten in Form von Graphen dar.

Abbildung 3 zeigt das Tool im Einsatz. IBM bietet für sein XMLbasiertes Logformat ein eigenes kostenloses Tool mit dem sprechenden Namen „IBM Pattern Modeling and Analysis Tool for Java Garbage Collection“ [14]. Thread Deadlocks und Loops können ein System stark belasten oder sogar zu einem kompletten Stillstand des Systems führen. Gerade wenn diese Situationen nur sehr selten auftreten, ist eine Analyse schwierig. Mit dem Thread Dump bietet Java aber auch hierfür eine funktionierende Lösung an. Der Threaddump beinhaltet alle Threads inklusive der Thread- Stacktraces zum Zeitpunkt der Erzeugung des Dumps.

HPjtune

Der Dump beinhaltet zudem Informationen über Monitore und blockierte Threads, die auf einen bestimmten Monitor warten. Deadlocks werden automatisch erkannt und die beteiligten Threads und Monitore zu Beginn des Dumps ausgegeben. Ein Threaddump kann wie ein Heapdump über Ctrl-\ oder Ctrl-Break oder mithilfe des mitgelieferten Tools jstack erzeugt werden. Das ThreadMXMBean liefert ab Java 5 zudem eine Operation zum Erzeugen des Dumps über JMX. Auch bei diesem Dump ist die Auswertung nicht immer trivial und zeitaufwändig. Das Thread Dump Analysis Tool [15] liefert eine kostenlose graphische Oberfläche zur Analyse. Auch IBM bietet für sein Format ein alphaWorks Tool mit dem Namen IBM Threaddump Analyzer [16].

Fazit und Ausblick

Der erste Teil dieser Artikelserie hat die Grundlagen von JVMTI und java.lang.instrument für die Analyse von Laufzeiten in Java beschrieben. Diese Technologie in Verbindung mit Bytecode-Instrumentierung ist die Basis für fast alle kommerziellen Profiler- und Diagnose-Tools. JMX und java. lang.management sowie JSR 77 für Java EE Monitoring liefern die Basisdaten für das Monitoring von Java Applikationen. Zudem ist JMX ein Framework um eigene Monitoring und Management-Funktionen in Applikationen einzubauen. Abgerundet werden diese Funktionen der JVM von den Log- und Dumpdateien, die eine detaillierte Analyse von Memory Leaks, Garbage Collection- und Thread-Problemen – auch in Produktionsumgebungen – erlaubt. Zusammengenommen ergibt sich ein Werkzeugkasten, der hilft, Performance- und Stabilitätsprobleme zu analysieren und zu beheben. Mithilfe der vorgestellten kostenfreien Utilities lässt sich die Analyse komfortabler und einfacher gestalten. Im zweiten Teil der Serie sollen kommerzielle Profiler, Diagnose- und Monitoring-Tools vorgestellt werden, die auf den hier vorgestellten Technologien basieren. Dabei liegt der Schwerpunkt auf der Auswahl eines Tools für konkrete Einsatzszenarien und den erweiterten Möglichkeiten für die Analyse und das Monitoring im Vergleich zu den hier vorgestellten Basisfunktionen.

Links & Literatur

[1] BCEL: jakarta.apache.org/bcel/

[2] The Java Virtual Machine Specification: java.sun.com/docs/books/jvms/

[3] ObjectWeb ASM: forge.objectweb.org/projects/asm/

[4] JBoss Javassist: labs.jboss.com/javassist/

[5] Hibernate JMX Statistics: www.hibernate.org/hib_docs/v3/reference/en/ html/session-configuration.html# configuration-optional-statistics/

[6] Spring JMX Support: static.springframework.org/ spring/docs/2.0.x/reference/jmx.html

[7] MC4J: mc4j.org

[8] Heap Analysis Tool: https://hat.dev.java.net

[9] JProbe: www.quest.com/jprobe/

[10] JProfiler: www.ej-technologies.com/products jprofiler/overview.html

[11] IBM HeapAnalyzer: www.alphaworks.ibm.com/tech/heapanalyzer/

[12] Sun Java HotSpot VM Options: java.sun.com/ javase/technologies/hotspot/vmoptions.jsp

[13] HPjtune: www.hp.com/products1/unix/java/ java2/hpjtune/

[14] IBM GC Analyzer: www.alphaworks.ibm.com/ tech/pmat/

[15] Thread Dump Analysis Tool: tda.dev.java.net

[16] IBM Thread and Monitor Dump Analyzer: www.alphaworks.ibm.com/tech/jca/

Zurück zu den Publikationen

Vollständiger Artikel