Java Performance Tools – Teil 1 (Grundlagen)
Java Performance Tools – Teil 1 (Grundlagen)
Java Performance Tools – Teil 1 (Grundlagen)
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Sonderdruck<br />
hinzugefügt oder ein bestehender Code<br />
verändert. Der Zeitpunkt der Manipulation<br />
ist allerdings je nach verwendeter<br />
Technologie unterschiedlich. Bei der statischen<br />
Bytecode-Instrumentierung wird<br />
der Bytecode vor dem Laden der Klassen<br />
mit einem Preinstrumentor instrumentiert.<br />
Der Bytecode läuft daher auf jeder<br />
JVM und es werden keine zusätzlichen<br />
Anpassungen oder Libraries zur Laufzeit<br />
benötigt. Nachteilig ist, dass ein manueller<br />
Schritt für die Instrumentierung notwendig<br />
ist und Systemklassen des Runtime<br />
Environment nicht instrumentiert<br />
werden können.<br />
Bei der Load-Time-Bytecode-Instrumentierung<br />
wird ein eigener Class-<br />
Loader verwendet, der beim Laden<br />
einer Klasse den entsprechenden Bytecode<br />
einfügt. Alternativ kann auch ein<br />
JVMPI-oder JVMTI-Agent verwendet<br />
werden, um beim entsprechenden Classloading<br />
Event den Inhalt der Klasse zu<br />
verändern. Man spart also im Gegensatz<br />
zum statischen Ansatz einen manuellen<br />
Schritt und kann auch Systemklassen instrumentieren.<br />
Dynamische Bytecode-Instrumentierung<br />
erlaubt es, den Bytecode einer Klasse<br />
auch nachdem sie bereits geladen ist, zu<br />
verändern. Diese Möglichkeit existiert<br />
erst seit <strong>Java</strong> 5 und wird daher erst von<br />
sehr wenigen <strong>Tools</strong> unterstützt. Das dynamische<br />
Instrumentieren von Klassen<br />
wird durch die RedefineClasses-Funktion<br />
in JVMTI möglich. Neben JVMTI bietet<br />
<strong>Java</strong> 5 aber auch eine reine <strong>Java</strong>-Implementierung<br />
von Agenten für die dynamische<br />
Instrumentierung von Bytecode,<br />
die sich im Paket java.lang.instrument<br />
befindet. Eigene Agenten zu schreiben, ist<br />
relativ einfach, aber nicht nur für <strong>Performance</strong>-Messungen<br />
sinnvoll, deshalb soll<br />
nachfolgend beispielhaft die Implementierung<br />
eines Agenten in <strong>Java</strong> beschrieben<br />
werden.<br />
Das Package java.lang.instrument<br />
bietet Agenten die Möglichkeit, den<br />
Bytecode von Klassen dynamisch zu<br />
verändern. Die Manipulation des Bytecodes<br />
kann auf zwei Wegen geschehen.<br />
Zum einen zur Ladezeit durch eine Implementierung<br />
des Interfaces ClassFile-<br />
Transformer. Zum anderen zur Laufzeit,<br />
das heißt, auch nachdem Klassen bereits<br />
durch einen ClassLoader geladen wurden.<br />
Letzteres geschieht durch die Methode<br />
redefineClasses(), welche im Interface<br />
Instrumentation definiert ist. Zur Veranschaulichung<br />
soll ein einfacher Agent<br />
entwickelt werden, der beim Laden jeder<br />
Klasse eine Meldung mit dem Namen der<br />
Klasse auf der Konsole ausgeben soll.<br />
Die Dokumentation des java.lang.instrument-Pakets<br />
gibt eine Übersicht über<br />
Anforderungen an einen Agenten und<br />
wie dieser eingebunden wird. Um einen<br />
eigenen Agenten zu entwickeln, wird eine<br />
Bootstrap-Klasse benötigt. Diese muss<br />
über eine Methode mit einer Signatur verfügen,<br />
die vergleichbar mit der main-Methode<br />
für <strong>Java</strong>-Applikationen ist:<br />
public static void premain(String agentArgs,<br />
...<br />
}<br />
Instrumentation instrumentation){<br />
Der Methode werden die Parameter für<br />
den Agenten übergeben sowie eine Instanz<br />
vom Typ java.lang.Instrumentation,<br />
die Funktionen zur Instrumentierung von<br />
<strong>Java</strong>-Klassen bietet. Die Veränderung des<br />
Bytecodes erfolgt entweder über eine eigeneClassFileTransformer-Implementierung<br />
für die Manipulation beim Laden<br />
oder Redefinition der Klasse oder direkt<br />
über eine neue Klassendefinition vom<br />
Typ ClassDefinition, wenn eine Klasse<br />
geändert werden soll, die bereits geladen<br />
ist. Wird der Bytecode einer bereits geladenen<br />
Klasse mit der redefineClasses()-<br />
Methode überschrieben, hat dies nur<br />
Auswirkungen auf zukünftig aufgerufene<br />
Instanzen der Klasse. Instanzen der Klasse<br />
die sich in einem aktiven Stack Frame<br />
befinden, bleiben von der Änderung unberührt.<br />
In dem Beispiel wird ein eigener<br />
ClassFileTransformer verwendet, um<br />
die Namen der geladenen Klassen auf die<br />
Konsole zu schreiben und sie dann unverändert<br />
weiterzureichen. Die Implementierung<br />
des Interface benötigt nur eine<br />
Methode:<br />
package de.codecentric.transformer;<br />
import java.lang.instrument.ClassFileTransformer;<br />
import java.lang.instrument.IllegalClassFormatException;<br />
import java.security.ProtectionDomain;<br />
public class LogClassFileTransformer implements<br />
ClassFileTransformer {<br />
public byte[] transform(ClassLoader loader,<br />
String className,<br />
Class classBeingRedefined,<br />
ProtectionDomain protectionDomain,<br />
byte[] classfileBuffer)<br />
throws IllegalClassFormatException {<br />
System.out.println(<br />
“Transformiere die Klasse ‘“ + className + “’“);<br />
return null;<br />
}<br />
}<br />
Nachdem der LogClassFileTransformer<br />
mit addTransformer() bei der Instrumentation-Instanz<br />
registriert wurde, wird bei<br />
jeder Definition oder Redefiniton einer<br />
Klasse die Methode transform(…) aufgerufen.<br />
Eine Klassendefiniton erfolgt<br />
über ClassLoader.definineClass() während<br />
Klassenredefinitionen über Instrumentation.redefineClasses()<br />
erfolgen.<br />
Innerhalb der Methode ist es möglich,<br />
den Bytecode der Klasse zu verändern.<br />
Der Bytecode muss dem Classfile-Format<br />
der JVM-Spezifikation [2] entsprechen.<br />
An dieser Stelle bieten sich zur vereinfachten<br />
Manipulation verschiedene<br />
Open-Source-<strong>Tools</strong> wie Apache BCEL,<br />
ObjectWeb ASM [3] oder JBoss <strong>Java</strong>ssist<br />
[4] an. In unserem Beispiel wird die<br />
Klasse nicht verändert, weshalb in der<br />
transform()-Methode null zurückgeliefert<br />
wird. Der Code für den Agenten<br />
sieht wie folgt aus:<br />
package de.codecentric.instrument;<br />
import java.lang.instrument.Instrumentation;<br />
import de.codecentric.transformer.<br />
LogClassFileTransfor-<br />
Sonderdruck www.javamagazin.de<br />
mer;<br />
public class JMVTIAgent {<br />
public static void premain(String agentArguments,<br />
Instrumentation instrumentation) {<br />
}<br />
}<br />
instrumentation.addTransformer(<br />
new LogClassFileTransformer());<br />
Der Agent kann jetzt mit jeder beliebigen<br />
<strong>Java</strong>-Anwendung zusammen gestartet