Obsah
1. Pohled pod kapotu JVM – využití rozhraní JNI společně s rozhraním JVM TI
2. Třicátý pátý demonstrační JVM TI agent – jeden ze způsobů řešení JAR Hellu :-)
3. Callback funkce callback_on_class_prepare()
4. Uživatelská funkce print_class_info()
5. Uživatelská funkce print_class_loader()
6. Uživatelská funkce print_path_to_class()
7. Zdrojový soubor Test35.java obsahující třídy a rozhraní nazvaná Test35, A, B a C
8. Spuštění 35.demonstračního agenta společně s třídou Test35
9. Spuštění třídy Test35 z Java archivu (JAR)
10. Zdrojové kódy 35.demonstračního agenta i k němu příslušných testovacích příkladů a skriptů
1. Pohled pod kapotu JVM – využití rozhraní JNI společně s rozhraním JVM TI
V předchozích částech seriálu o programovacím jazyku Java i o virtuálním stroji Javy jsme se při popisu callback funkcí volaných JVM TI rozhraním setkali s tím, že se do těchto funkcí předávají minimálně dva ukazatele. První z těchto ukazatelů je typu jvmtiEnv* a jde o vstupní bod ke všem funkcím rozhraní JVM TI, zatímco druhý ukazatel je typu JNIEnv* a s jeho využitím lze volat funkce nabízené rozhraním JNI (Java Native Interface). Toto rozhraní se v JVM TI agentech může využívat různým způsobem, zejména však pro vytváření javovských objektů a volání metod těchto objektů. V tomto seriálu jsme si totiž již popsali prakticky veškerou funkcionalitu rozhraní JVM TI, ovšem přímou manipulací s javovskými objekty jsme se nezabývali – a ani nemohli, protože JVM TI neduplikuje funkce, které jsou nabízené právě JNI.
Většina vývojářů, kteří se s rozhraním JNI setkali, využívala toto rozhraní k volání nativních funkcí/metod z Javy, což například umožňuje načíst a posléze přímo z Javy využívat prakticky jakoukoli nainstalovanou nativní knihovnu. My ovšem budeme JNI používat přesně opačným způsobem – budeme volat funkce tohoto rozhraní z céčka tak, abychom napodobili manipulaci s objekty v Javě. V dalších kapitolách se setkáme s následujícími funkcemi rozhraní JNI:
# | Funkce JNI | Význam |
---|---|---|
1 | FindClass() | nalezení třídy na základě její plné signatury |
2 | GetMethodID() | získání nestatické metody na základě jejího jména, signatury a třídy |
3 | CallObjectMethod() | zavolání metody s předáním parametrů a získáním výsledku |
4 | NewStringUTF | vytvoření instance třídy java.lang.String s inicializací céčkovým řetězcem |
5 | GetStringUTFChars() | převod mezi java.lang.String a céčkovým řetězcem |
6 | ReleaseStringUTFChars() | uvolnění instance třídy java.lang.String |
2. Třicátý pátý demonstrační JVM TI agent – jeden ze způsobů řešení JAR Hellu :-)
Způsob využití funkcí nabízených rozhraním JNI si ukážeme v třicátém pátém demonstračním JVM TI agentovi na výpisu souborů (streamů), z nichž jsou získávány bajtkódy tříd načítaných do virtuálního stroje Javy. Připomeňme si, že v JVM se načítání tříd (přesněji řečeno jejich bajtkódů) provádí přes takzvané classloadery, přičemž přímo v JVM je implementován takzvaný bootstrap classloader použitý pro načítání „systémových“ tříd a další classloadery se již mohou inicializovat právě s využitím bootstrap classloaderu. Bajtkódy tříd jsou většinou načítány přímo ze souborů .class, které jsou hledány v adresářích specifikovaných v proměnné prostředí CLASSPATH, popř. definovaných při startu virtuálního stroje (přepínač -cp). Ve skutečnosti se však mohou bajtkódy načítat i z dalších míst, například z Java archivů (soubory s koncovkou .jar), bajtkód lze přenést i přes síťové rozhraní atd.
V některých případech – a možná častěji, než je zdrávo – může dojít k situaci, kdy je do virtuálního stroje Javy načítána stejná třída, která může být například uložena v různých Java archivech – programátor či administrátor například nasazuje aplikaci na aplikační server, v němž je již nainstalována jiná verze stejné knihovny atd. V těchto situacích záleží chování aplikace na mnoha okolnostech a v případě, že se aplikace chová jinak než při vývoji, je vhodné získat informace o tom, jaké třídy a s využitím jakých classloaderů se načítají. Některé z těchto informací lze získat s využitím přepínače -verbose:class, ovšem zde se již nevypíše, s využitím jakého classloaderu se daná třída načítá. A právě zde se může – samozřejmě kromě dalších sofistikovanějších nástrojů – použít i náš demonstrační JVM TI agent popsaný v následujících kapitolách.
3. Callback funkce callback_on_class_prepare()
I v třicátém pátém demonstračním JVM TI agentovi je, podobně jako i v několika agentech popsaných v předchozích dílech tohoto seriálu, zaregistrována callback funkce nazvaná callback_on_class_prepare(), která je zavolána ve chvíli, kdy je do virtuálního stroje Javy načítána nějaká třída a kdy je zpracován její bajtkód (důležité pro nás je, že třída je již ve chvíli volání funkce callback_on_class_prepare() skutečně načtena, protože nad ní budeme provádět další operace). V této callback funkci se nejprve přes rozhraní JVM TI získá signatura třídy, která je následně upravena do čitelné podoby. Posléze je zavolána uživatelská funkce nazvaná print_class_info(), která bude podrobněji popsána v následující kapitole:
/* * Callback funkce zavolana ve chvili, kdy je trida ve virtualnim stroji ve stavu, * kdy ji lze normalne pouzivat. */ static void JNICALL callback_on_class_prepare( jvmtiEnv *jvmti_env, JNIEnv *jni_env, jthread thread, jclass class) { jvmtiError error; char *class_name_ptr; char *updated_class_name_ptr; enter_critical_section(jvmti_env); /* ziskat jmeno tridy */ error = (*jvmti_env)->GetClassSignature(jvmti_env, class, &class_name_ptr, NULL); check_jvmti_error(jvmti_env, error, "get class signature"); if (class_name_ptr == NULL) { puts("Error: class has no signature"); } /* upravit jmeno tridy */ updated_class_name_ptr = update_class_name(class_name_ptr); /* vypsat informace o classloaderu a streamu, z nehoz je nacten bajtkod tridy */ print_class_info(jvmti_env, jni_env, class, updated_class_name_ptr); /* dealokace pameti po GetClassSignature() */ error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr); check_jvmti_error(jvmti_env, error, "deallocate class name"); exit_critical_section(jvmti_env); }
4. Uživatelská funkce print_class_info()
Uživatelská funkce print_class_info() je volána z callback funkce callback_on_class_prepare() popsané ve třetí kapitole. V této funkci nejdříve získáme classloader pro třídu specifikovanou hodnotou typu jclass. Pro získání classloaderu slouží JVM TI funkce GetClassLoader(), které se musí ve druhém parametru předat ukazatel na proměnnou typu jobject. Přes tento ukazatel je buď vrácen příslušný classloader, nebo se vrátí hodnota NULL, a to tehdy, pokud je třída načítána bootstrap classloaderem (jedná se tedy většinou o „systémovou“ třídu). Ve chvíli, kdy je skutečně získán classloader, jsou zavolány další dvě uživatelské funkce print_class_loader() (kapitola 5) a print_path_to_class() (kapitola 6). Získaný classloader by měl být explicitně uvolněn pomocí JNI funkce DeleteLocalRef(), protože se jedná o takzvanou lokální referenci. Ve skutečnosti to ovšem nemusí být nutné, jelikož se JVM postará o automatické uvolnění až šestnácti lokálních referencí v jednom vláknu:
/* * Tisk informaci o nactene tride: classloaderu a ceste ke streamu * z nehoz se nacita bajtkod */ static void print_class_info( jvmtiEnv *jvmti_env, JNIEnv *jni_env, jclass class, char *class_name) { jobject class_loader = NULL; /* ziskani classloaderu tridy */ (*jvmti_env)->GetClassLoader(jvmti_env, class, &class_loader); /* bootstrap classloader slouzi vetsinou k nacteni trid ulozenych v rt.jar */ if (class_loader == NULL) { printf("%-50s bootstrap classloader\n", class_name); } else { print_class_loader(jvmti_env, jni_env, class_loader, class_name); print_path_to_class(jvmti_env, jni_env, class_loader, class_name); } }
5. Uživatelská funkce print_class_loader()
Ve funkci print_class_loader() se již setkáme s přímým využitím rozhraní JNI. Jedná se především o funkci GetMethodID() sloužící pro získání metody se zadaným jménem, signaturou a třídou, dále pak o funkci FindClass() sloužící pro nalezení třídy specifikované svým jménem, funkci CallObjectMethod(), která dokáže zavolat nalezenou metodu a předat jí parametry a nakonec o dvojici funkcí GetStringUTFChars() a ReleaseStringUTFChars() sloužících pro manipulaci s řetězci (je totiž rozdíl pracovat s řetězci virtuálního stroje Javy a řetězci céčkovými). Vše, co uživatelská funkce print_class_loader() dělá, by se v Javě dalo shrnout do jediného řádku:
System.out.println(java.lang.ClassLoader.toString());
V JVM TI agentovi ovšem musíme tento kód přepsat do volání funkcí rozhraní JNI, což je již docela zdlouhavé:
/* * Vypis classloaderu spjateho s konkretni Javovskou tridou. */ static void print_class_loader( jvmtiEnv *jvmti_env, JNIEnv *jni_env, jclass class_loader, char *class_name) { /* nalezeni metody java.lang.ClassLoader.toString() */ jmethodID to_string = (*jni_env)->GetMethodID(jni_env, (*jni_env)->FindClass(jni_env, "java/lang/ClassLoader"), "toString", "()Ljava/lang/String;" ); if (to_string == NULL) { MSG("can not find method ClassLoader.toString()"); return; } /* zavolani metody java.lang.ClassLoader.toString() */ jstring jstr = (jstring)(*jni_env)->CallObjectMethod(jni_env, class_loader, to_string); if (jstr == NULL) { MSG("can not call method ClassLoader.toString()"); return; } /* prevod objektu typu jstring na ceckovy retezec */ char *str = (char*)(*jni_env)->GetStringUTFChars(jni_env, jstr, NULL); printf("%-50s other classloader %s\n", class_name, str); /* uvolneni alokovane pameti */ (*jni_env)->ReleaseStringUTFChars(jni_env, jstr, str); }
6. Uživatelská funkce print_path_to_class()
Další uživatelská funkce nazvaná print_path_to_class() má za úkol vypsat na standardní výstup cestu k souboru (či korektněji řečeno ke streamu), z něhož byl získán bajtkód představující načítanou třídu. Ekvivalentem kódu implementovaného v této funkci je následující řádek v Javě:
System.out.println(java.lang.ClassLoader.getResource(className+".class").toExternalForm());
Zajisté již tušíte, že céčkový ekvivalent využívající rozhraní JNI tak stručný nebude :-) Skutečně je tomu tak, neboť je nejprve nutné vytvořit řetězec obsahující className+".class, následně získat metodu ClassLoader.getResource(), zavolat tuto metodu a předat jí vytvořený řetězec (ovšem převedený na jinou formu) a následně zpracovat výsledek této metody, což je objekt typu java.net.URL. Nad tímto objektem je totiž nutné zavolat metodu toExternalForm() a vypsat její návratovou hodnotu (řetězec) na standardní výstup, samozřejmě opět s konverzí mezi řetězci JVM a céčkovými řetězci:
/* * Vypis cesty k souboru (streamu), z nehoz je nacitan bajtkod tridy. */ static void print_path_to_class( jvmtiEnv *jvmti_env, JNIEnv *jni_env, jclass class_loader, char *class_name) { /* ze jmena tridy "Trida" potrebujeme vytvorit retezec "Trida.class" */ char *upd_class_name = (char*)malloc(strlen(class_name)+7); strcpy(upd_class_name, class_name); strcat(upd_class_name, ".class"); /* nalezeni metody java.lang.ClassLoader.getResource() */ jmethodID get_resource = (*jni_env)->GetMethodID(jni_env, (*jni_env)->FindClass(jni_env, "java/lang/ClassLoader"), "getResource", "(Ljava/lang/String;)Ljava/net/URL;" ); if (get_resource == NULL) { MSG("can not find method ClassLoader.getResource()"); return; } /* zavolani metody java.lang.ClassLoader.getResource() */ jobject url = (*jni_env)->CallObjectMethod(jni_env, class_loader, get_resource, (*jni_env)->NewStringUTF(jni_env, upd_class_name)); if (url == NULL) { MSG("can not call method ClassLoader.getResource()"); return; } /* nalezeni metody java.net.URL.toExternalForm() */ jmethodID to_external_form = (*jni_env)->GetMethodID(jni_env, (*jni_env)->FindClass(jni_env, "java/net/URL"), "toExternalForm", "()Ljava/lang/String;" ); if (to_external_form == NULL) { MSG("can not find method URL.toExternalForm()"); return; } /* zavolani metody java.net.URL.toExternalForm() */ jstring jstr = (jstring)(*jni_env)->CallObjectMethod(jni_env, url, to_external_form); if (jstr == NULL) { MSG("can not call method URL.toExternalForm()"); return; } /* prevod objektu typu jstring na ceckovy retezec */ char *str = (char*)(*jni_env)->GetStringUTFChars(jni_env, jstr, NULL); printf("Class %s is loaded from: %s\n", upd_class_name, str); /* uvolneni alokovane pameti */ (*jni_env)->ReleaseStringUTFChars(jni_env, jstr, str); }
7. Zdrojový soubor Test35.java obsahující třídy a rozhraní nazvané Test35, A, B a C
Pro otestování správné funkce třicátého pátého demonstračního JVM TI agenta byl vytvořen javovský zdrojový soubor nazvaný Test35.java. Interně je tento zdrojový soubor poměrně komplikovaný protože je v něm definována veřejná třída Test35, neveřejná třída A, statická vnitřní třída B a taktéž neveřejné rozhraní C. V tomto rozhraní je předepsána metoda void foo() a rozhraní je implementováno v anonymní třídě vytvořené přímo v metodě main().
Po překladu tohoto zdrojového kódu získáme následující soubory s bajtkódem:
# | Třída/rozhraní | Bajtkód uložen v souboru |
---|---|---|
1 | Třída Test35 | Test35.class |
2 | Třída A | A.class |
3 | Vnitřní třída B | Test35$B.class |
4 | Rozhraní C | C.class |
5 | Anonymní třída implementující C | Test35$1.class |
Pod tímto odstavcem je vypsán obsah zdrojového textu Test35.java:
import java.awt.Color; import java.util.*; interface C { void foo(); } class A { } /** * Testovaci trida pouzita pro test tricateho * pateho demonstracniho JVM TI agenta. * * Trida obsahuje nekolik metod s ruznym poctem * a typy lokalnich promennych. */ public class Test35 { static class B { } private void method1() { } private void method2() { int x = 0; } private void method3() { int x = 1; int y = 2; } private void method4() { byte byte_variable = 1; short short_variable = 2; int int_variable = 3; long long_variable = 4; } private void method5() { char char_variable = 'a'; float float_variable = 1/2f; double double_variable = 1/2.0; boolean boolean_variable = true; } private void method6() { int[] int_array = null; float[] float_array = null; int[][] int_matrix = null; } private void method7() { String str = null; Color color = null; Color[] colors = null; } private void method8() { List<String> list = null; Set<Integer> set = null; Map<Float, String> map = null; Queue<Color> queue = null; Deque<String> double_ended_queue = null; } private void method9(int x, float y, boolean z) { List<String> list = null; Set<Integer> set = null; Map<Float, String> map = null; Queue<Color> queue = null; Deque<String> double_ended_queue = null; } private static void static_method1() { } private static void static_method2() { int x = 0; } private static void static_method3() { int x = 1; int y = 2; } private static void static_method4() { byte byte_variable = 1; short short_variable = 2; int int_variable = 3; long long_variable = 4; } private static void static_method5() { char char_variable = 'a'; float float_variable = 1/2f; double double_variable = 1/2.0; boolean boolean_variable = true; } private static void static_method6() { int[] int_array = null; float[] float_array = null; int[][] int_matrix = null; } private static void static_method7() { String str = null; Color color = null; Color[] colors = null; } private static void static_method8() { List<String> list = null; Set<Integer> set = null; Map<Float, String> map = null; Queue<Color> queue = null; Deque<String> double_ended_queue = null; } private static void static_method9(int x, float y, boolean z) { List<String> list = null; Set<Integer> set = null; Map<Float, String> map = null; Queue<Color> queue = null; Deque<String> double_ended_queue = null; } /** * Spusteni testu. */ public static void main(String[] args) { new A(); new B(); C c = new C() { public void foo() { System.out.println("foo"); } }; } }
8. Spuštění 35.demonstračního agenta společně s třídou Test35
Překlad třicátého pátého demonstračního JVM TI agenta se provede příkazem:
gcc -Wall -ansi -I/usr/lib/jvm/java-1.6.0-openjdk/include/ -shared -o libagent35.so agent35.c
Překlad testovacího zdrojového souboru Test35 zajistí jednoduše příkaz (volba -g zde není ve skutečnosti nutná):
javac -g Test35.java
Po překladu se vytvoří několik souborů .class s bajtkódem, jejichž obsah byl vysvětlen v předchozí kapitole. Nyní již můžeme demonstračního agenta spustit, a to konkrétně příkazem:
java -agentpath:./libagent35.so Test35 2> /dev/null
Po spuštění agenta by se na standardní výstup měly vypsat následující řádky (vysvětlení jednotlivých zpráv hledejte na konci kapitoly):
Agent35: Agent_OnLoad Agent35: JVM TI version is correct Agent35: Got VM init event java.lang.ClassNotFoundException bootstrap classloader java.net.URLClassLoader$1 bootstrap classloader sun.misc.URLClassPath$3 bootstrap classloader sun.misc.URLClassPath$Loader bootstrap classloader sun.misc.URLClassPath$JarLoader bootstrap classloader java.lang.StringBuffer bootstrap classloader java.lang.Short bootstrap classloader sun.misc.URLClassPath$JarLoader$1 bootstrap classloader sun.misc.FileURLMapper bootstrap classloader java.util.zip.ZipConstants bootstrap classloader java.util.zip.ZipFile bootstrap classloader java.util.jar.JarFile bootstrap classloader ... ... ... Test35 other classloader sun.misc.Launcher$AppClassLoader@17182c1 sun.misc.Launcher$1 bootstrap classloader java.io.IOException bootstrap classloader java.io.FileNotFoundException bootstrap classloader java.net.URLClassLoader$2 bootstrap classloader Class Test35.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35.class A other classloader sun.misc.Launcher$AppClassLoader@17182c1 Class A.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/A.class Test35$B other classloader sun.misc.Launcher$AppClassLoader@17182c1 Class Test35$B.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35$B.class C other classloader sun.misc.Launcher$AppClassLoader@17182c1 Class C.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/C.class Test35$1 other classloader sun.misc.Launcher$AppClassLoader@17182c1 Class Test35$1.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35$1.class ... ... ... java.util.ArrayList$Itr bootstrap classloader java.util.IdentityHashMap$KeySet bootstrap classloader java.util.IdentityHashMap$IdentityHashMapIterator bootstrap classloader java.util.IdentityHashMap$KeyIterator bootstrap classloader java.io.DeleteOnExitHook bootstrap classloader java.util.LinkedHashSet bootstrap classloader java.util.HashMap$KeySet bootstrap classloader java.util.LinkedHashMap$LinkedHashIterator bootstrap classloader java.util.LinkedHashMap$KeyIterator bootstrap classloader java.util.Collections bootstrap classloader java.util.Collections$EmptySet bootstrap classloader java.util.Collections$EmptyList bootstrap classloader java.util.Collections$EmptyMap bootstrap classloader Agent35: Got VM Death event Agent35: Agent_OnUnload
V předchozím výpisu se objevily hned tři zajímavé informace. První z nich je, že základní „systémové“ třídy jsou načítané bootstrap classloaderem, což je očekávaná vlastnost JVM (ne všechny virtuální stroje Javy se ovšem přesně takto chovají). Druhou informací je to, že agent skutečně dokázal vypsat cesty k bajtkódům, a to jak u tříd, tak i u rozhraní C (které se musí načíst společně s anonymní třídou Test35$1:
Class Test35.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35.class Class A.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/A.class Class Test35$B.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35$B.class Class C.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/C.class Class Test35$1.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35$1.class
Možná nejzajímavější informace je však skryta v řádcích:
Test35 other classloader sun.misc.Launcher$AppClassLoader@17182c1 sun.misc.Launcher$1 bootstrap classloader java.io.IOException bootstrap classloader java.io.FileNotFoundException bootstrap classloader java.net.URLClassLoader$2 bootstrap classloader Class Test35.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35.class
Můžeme zde vidět, že mezi výpisem zprávy o classloaderu třídy Test35 a výpisem informace o tom, odkud byl načten její bajtkód, se „magicky“ do virtuálního stroje Javy načetly čtyři další třídy. O toto načtení jsme se vlastně postarali sami v JVM TI agentovi, protože se tyto třídy použily pro získání streamu s bajtkódem.
Poznámka: podobný výstup dostaneme i v případě, že se při startu virtuálního stroje Javy použije přepínač -verbose:class:
[Loaded java.lang.Object from shared objects file] [Loaded java.io.Serializable from shared objects file] [Loaded java.lang.Comparable from shared objects file] [Loaded java.lang.CharSequence from shared objects file] [Loaded java.lang.String from shared objects file] ... ... ... [Loaded Test35 from file:/home/pavel/temp/jvmti/Agent35/] [Loaded A from file:/home/pavel/temp/jvmti/Agent35/] [Loaded Test35$B from file:/home/pavel/temp/jvmti/Agent35/] [Loaded C from file:/home/pavel/temp/jvmti/Agent35/] [Loaded Test35$1 from file:/home/pavel/temp/jvmti/Agent35/] ... ... ...
9. Spuštění třídy Test35 z Java archivu (JAR)
V předchozí kapitole jsme mohli vidět, jak se JVM TI agent chová v případě, že jsou třídy načítány přímo ze souborů .class. Co se však stane v případě, že třídy zabalíme do Java archivu (JAR) příkazem:
jar cfe test.jar Test35 *.class
?
Je jisté, že virtuální stroj Javy je nutné spouštět poněkud odlišným způsobem, konkrétně takto:
java -agentpath:./libagent35.so -jar test.jar Test35 2> /dev/null
Virtuální stroj Javy bude v tomto případě načítat všech pět souborů s bajtkódem z Java archivu nazvaného test.jar a výpis provedený agentem se tedy bude oproti předchozímu výpisu odlišovat:
Agent35: Agent_OnLoad Agent35: JVM TI version is correct Agent35: Got VM init event java.util.zip.ZipConstants bootstrap classloader java.util.zip.ZipFile bootstrap classloader java.util.jar.JarFile bootstrap classloader sun.misc.JavaUtilJarAccess bootstrap classloader java.util.jar.JavaUtilJarAccessImpl bootstrap classloader java.util.zip.ZipEntry bootstrap classloader java.util.jar.JarEntry bootstrap classloader java.util.jar.JarFile$JarFileEntry bootstrap classloader java.io.DataInput bootstrap classloader java.io.DataInputStream bootstrap classloader ... ... ... Test35 other classloader sun.misc.Launcher$AppClassLoader@13f5d07 sun.misc.Launcher$1 bootstrap classloader java.io.IOException bootstrap classloader java.io.FileNotFoundException bootstrap classloader java.net.URLClassLoader$2 bootstrap classloader Class Test35.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/Test35.class A other classloader sun.misc.Launcher$AppClassLoader@13f5d07 Class A.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/A.class Test35$B other classloader sun.misc.Launcher$AppClassLoader@13f5d07 Class Test35$B.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/Test35$B.class C other classloader sun.misc.Launcher$AppClassLoader@13f5d07 Class C.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/C.class Test35$1 other classloader sun.misc.Launcher$AppClassLoader@13f5d07 Class Test35$1.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/Test35$1.class java.util.ArrayList$Itr bootstrap classloader java.util.IdentityHashMap$KeySet bootstrap classloader java.util.IdentityHashMap$IdentityHashMapIterator bootstrap classloader java.util.IdentityHashMap$KeyIterator bootstrap classloader java.io.DeleteOnExitHook bootstrap classloader java.util.LinkedHashSet bootstrap classloader java.util.HashMap$KeySet bootstrap classloader java.util.LinkedHashMap$LinkedHashIterator bootstrap classloader java.util.LinkedHashMap$KeyIterator bootstrap classloader java.util.Collections bootstrap classloader java.util.Collections$EmptySet bootstrap classloader java.util.Collections$EmptyList bootstrap classloader java.util.Collections$EmptyMap bootstrap classloader Agent35: Got VM Death event Agent35: Agent_OnUnload
Podobný výstup získáme i při použití volby -verbose:class:
[Loaded java.lang.Object from shared objects file] [Loaded java.io.Serializable from shared objects file] [Loaded java.lang.Comparable from shared objects file] [Loaded java.lang.CharSequence from shared objects file] [Loaded java.lang.String from shared objects file] ... ... ... [Loaded Test34 from file:/home/pavel/temp/jvmti/Agent35/test.jar] [Loaded A from file:/home/pavel/temp/jvmti/Agent35/test.jar] [Loaded Test34$B from file:/home/pavel/temp/jvmti/Agent35/test.jar] [Loaded C from file:/home/pavel/temp/jvmti/Agent35/test.jar] [Loaded Test34$1 from file:/home/pavel/temp/jvmti/Agent35/test.jar] ... ... ...
Porovnejme si nyní zprávy vypsané při spuštění třídy Test35 přímo:
Class Test35.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35.class Class A.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/A.class Class Test35$B.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35$B.class Class C.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/C.class Class Test35$1.class is loaded from: file:/home/pavel/temp/jvmti/Agent35/Test35$1.class
…se zprávami vypsanými při spuštění téže třídy, ovšem uložené v Java archivu:
Class Test35.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/Test35.class Class A.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/A.class Class Test35$B.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/Test35$B.class Class C.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/C.class Class Test35$1.class is loaded from: jar:file:/home/pavel/temp/jvmti/Agent35/test.jar!/Test35$1.class
Agent tedy správně rozpoznal, že se soubory s bajtkódem nyní načítají z Java archivu.
10. Zdrojové kódy 35.demonstračního agenta i k němu příslušných testovacích příkladů a skriptů
Zdrojový kód třicátého pátého demonstračního JVM TI agenta je, společně s testovací třídou nazvanou Test35 i se skripty použitými pro překlad a spuštění agenta (dvěma způsoby), uložen do Mercurial repositáře dostupného na adrese http://icedtea.classpath.org/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze všech zmíněných zdrojových souborů můžete najít na těchto adresách:
JVM TI agent #35 | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/1d908b81dc34/jvmti-agents/agent35/agent35.c |
Testovací třída Test35 | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/1d908b81dc34/jvmti-agents/agent35/Test35.java |
Skript pro vytvoření Java archivu | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/1d908b81dc34/jvmti-agents/agent35/makejar.sh |
Skript pro překlad agenta #35 | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/1d908b81dc34/jvmti-agents/agent35/compile.sh |
Skript pro spuštění agenta #35 | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/1d908b81dc34/jvmti-agents/agent35/test.sh |
Skript pro spuštění agenta #35 proti JARu | http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/1d908b81dc34/jvmti-agents/agent35/test2.sh |
11. Odkazy na Internetu
- Breakpoint (Wikipedia)
http://cs.wikipedia.org/wiki/Breakpoint - JVM Tool Interface Version 1.2 Documentation
http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html - JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#SetBreakpoint - JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#ClearBreakpoint - JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
http://docs.oracle.com/javase/6/docs/platform/jvmti/jvmti.html#Breakpoint - The JVM Tool Interface (JVM TI): How VM Agents Work
http://www.oracle.com/technetwork/articles/javase/jvm-ti-141370.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html