Prinzipielles Vorgehen beim Profiling
Das Profiling erfolgt über das JVM Tool Interface (JVMTI). Ab Java 5 ersetzt diese Schnittstelle das JVMPI (JVM Profiling Interface) und JVMDI (JVM Debugging Interface). An diese Schnittstelle kann sich ein Profiling-Agent anmelden, der sich an Ereignisse der JVM bindet und die dabei anfallenden Daten sammelt. Diese Daten werden dann von einem geeigneten Tool aufbereitet und visualisert. Jedes Profiling besitzt damit die Bestandteile
Zeitverhalten oder Speicherbelastung
Beim Profiling lassen sich grob zwei Sachverhalte messen und untersuchen:
Heapdumps mit HPROF
HPROF ist ein experimenteller Profiling Agent von Sun. Kleiner Footprint, erzeugt riesiege Dumpfiles, die mit einem Tool untersucht werden können. Geeignet für schnelle Ergebnisse auch im professionellen Bereich.
Heapdumps mit JMX
Ab dem JRE 1.6 bietet JMX eine komfortable Möglichkeit für das Erzeugen von Heapdumps zur Laufzeit der Anwendung. Mit der Startoption
-Dcom.sun.management.jmxremotelässt sich eine Anwendung remote administrieren. Dazu jconsole starten und mit dem Prozess der Anwendung verbinden. Anschließend unter MBeans com.sun.management.HotSpotDiagnostic anwählen und mit dumpHeap einen Heap-Dump erzeugen (vorher als Argument einen Namen für das Dumpfile angeben!).
Colorer
Eclipse Colorer ist ein freies Eclipse Plugin mit mittlerem Footprint. Läuft leider nur noch mit Eclipse 3.0 und wird aktuell nicht weiterentwickelt.
TPTP bis Java1.4 - Trace and Profiling Tools Project
Bestandteil im Eclipse-Callisto Release basierend auf dem Java Virtual Machine Profiler Interface (JVMPI, experimentell für Suns JVM Implementierungen).
Installation von TPTP (lokal): (Stand: Juni 2007) In den letzten Monaten hat die Callisto-Entwicklergemeinde ordentlich einen drauf gelegt. TPTP läuft inzwischen zuverlässig und für lokales Profiling kann eine direkte Verbindung zur VM genutzt werden (anstatt einen Client oder Dienst starten zu müssen). Die Installation vereinfacht sich entsprechend.
Error occurred during initialization of VM Could not find agent library on the library path or in the local directory: piAgentdann bitte in der Eclipse Installation unter plugins nach der Datei piAgent.dll suchen und diesen Pfad eintragen.
TPTP seit Java 5 - Test and Performance Tools Plattform
Gleiches Akronym mit etwas modifizierter Übersetzung, nun konform zur JVMTI Schnittstelle und bestens integriert in die Eclipse IDE (TPTP).
Profiling der JRockit - JRCMD
Die aktuelle Version von JRockit beinhaltet eine Tool mit dem man grundlegende JVM Eigenschaften untersuchen kann. Es eignet sich hervorragend für eine schnelle Analyse bei Laufzeitproblemen mit der JRockit JVM und man kann es intuitiv erlernen. 'jrcmd' ohne Parameter gibt alle laufenden JRockit JVM Prozesse und den Namen der Main-Klasse.
> jrcmd > 1825 weblogic.Server > 2334 com.util.LauncherAuf einen dieser Prozesse bezieht man sich nun. Aber zunächst kann man sich mit der help-Option alle Kommandos anschauen. Noch mehr Informationen gibt es mit jrcmd
memprof: Memory leak detection
sampleRate - number of seconds between samples (default: 10s)
trendSize - number of positive samples to consider (default: 5)
forceThreshold - size in bytes to force full stack stats (default: 250k)
verboseResultStats - print all known stats, not just leaking (default: false)
runsystemgc: java.lang.System.gc()
runfinalization: java.lang.System.runFinalization()
heap_diagnostics: heap diagnostic
print_class_summary: alle geladenen Klassen
print_object_summary: Objekte auf dem Heap
name1, name2, name3 - Klassennamen, die gezeigt werden sollen (mit / statt .)
increaseonly - (bool) zeige nur Klassen die zunehmen
largestarrays - zeige die 10 größten Arrays vom Typ name1
activatetrendanalysis - Trendanalysis, relativer Zuwachs nach jeder GC.
deactivatetrendanalysis - Trendanalyse aus
print_properties -> int the Java and VM properties.
Echte Analysen brauchen auch hier Werkzeuge, die die enorme Datenflut beim Profiling visuell aufbereiten. Hier lassen sich mit
jrcmd 1234 jrarecording time=600 filename=prof.xml.zipProfiling-Dumpfiles erzeugen und anschließend mittels eines GUI (der JRA) untersuchen. Ein Profil kann übrigens auch über Startoptionen des Servers oder über die Managementkonsole angelegt werden. Leider müssen für diese Profiling-Funktionen extra Lizenzen gekauft werden.
Manipulation von java.lang.Object
Kommt ein Einsatz professioneller Profiling-Werkzeuge nicht in Frage, kann man vielleicht auf der Sprachebene improvisieren. So ließen sich alle angelegten Objektinstanzen zählen, indem man im Konstruktor von java.lang.Object (den ja alle Konstruktoren durchlaufen) einen Zähler hochzählt und in finalize entsprechend runterzählt.
Dazu ist zunächst die Klasse java.lang.Object entsprechend zu manipulieren. Aus den Java-Quellen legt man sich eine Kopie von java.lang.Object in den Workspace und compiliert die Klasse ganz normal mit dem JDK:
public class Object {
:
public Object() {
System.out.println(this.getClass().getName());
}
:
}
Startet man nun ein Testprogramm passiert - zunächst garnichts. Ja, richtig. Hier ist zwar eine manipulierte Version der Klasse java.lang.Object erstellt worden, allerdings nimmt die Laufzeit standardmäßig die Klassen des JRE, da diese Bestandteil des Bootclasspath sind. Es muss deshalb beim Starten der Anwendung der Bootclasspath manipuliert werden:
-Xbootclasspath/p: <Pfad zur manipulierten java.lang.Object.class>Das Programm wird erneut gestartet und nun passiert etwas:
Error occurred during initialization of VM java.lang.StackOverflowErrorDer Grund dafür findet sich in der rekursiven Verwendung eines Konstruktors innerhalb des Konstruktors von java.lang.Object. Die Zeile System.out.println(this.getClass().getName()) erzeugt Objektinstanzen, die wiederum durch den Konstruktor von java.lang.Object laufen müssen und so fort. Eine Möglichkeit zur Behebung des Problems besteht darin, keine Konstruktoren innerhalb des manipulierten Konstruktors von java.lang.Object zu rufen. Da aus genannten Gründen auch im statischen und nichtstatischen Initialisierungsbereich von java.lang.Object keine Objektinstanzen angelegt werden dürfen, ist dieses Vorgehen nutzlos. Es muss eine andere Lösung gefunden werden.
Innerhalb eines synchronisierten Bereichs im Konstruktor von java.lang.Object wird ein Flag gesetzt, das dafür sorgt, dass während der Abarbeitung nun keine Objektinstanzen erzeugt werden. Beim Verlassen des Konstruktors wird das Flag wieder zurückgesetzt.
public class Object {
:
private static boolean inConstructor = true;
public Object() {
synchronized(Object.class) {
if (!inConstructor) {
inConstructor = true;
System.out.println(this.getClass().getName());
inConstructor = false;
}
}
}
public static void trace() {
synchronized(Object.class) {
inConstructor = false;
}
}
:
}
:
public static void main(String[] args) {
Object.trace();
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.show();
}
:
Bei diesem Vorgehen werden die Objektinstanzen innerhalb des Konstruktors von java.lang.Object und vor dem Aufruf von Object.trace() nicht bei der Abarbeitung erfasst. Das ist für die Zwecke des Profilings normalerweise auch nicht notwendig. Unbedingt hinzugefügt werden muss, dass derart manipulierte Laufzeiten nicht ausgeliefert werden dürfen.
Auswerten eines Heap-Abbildes: Finden von Speicherlecks
Objektinstanzen werden nicht von der GC aufgeräumt, wenn sie von einer GC Root direkt oder indirekt referenziert werden. Das sind in jedem Fall:
:
{
Object obj = new Object();
}
Thread.wait(); // kann lange dauern: obj wird nicht abgeräumt
:
Dominator Tree Analysis
Eine Begutachtung des physikalischen Objektgraphs hilft also normalerweise nicht weiter. Was wirklich interessiert ist die Information, ob eine Instanz A der "Dominator" einer Instanz B ist. Das ist dann der Fall, wenn alle (!) Referenzpfade einer GC Root zu B durch A gehen. In einem Dominator Tree wird diese Relation als Hierarchie dargestellt. Außerdem wird der Speicher aufaddiert, der von einer Instanz und all seinen dominierten Instanzen belegt wird (das sogenannte Retain Set wird ausgewertet). Es zeigt sich nun folgendes: