Pohled pod kapotu JVM - využití rozhraní JNI společně s rozhraním JVM TI

19. 3. 2013
Doba čtení: 21 minut

Sdílet

Ilustrační obrázek
Autor: Depositphotos – stori
Ilustrační obrázek
Dnes si společně ukážeme způsob využití rozhraní JNI (Java Native Interface) z JVM TI agentů. JVM TI agenti, což jsou programy napsané většinou v C či C++, totiž v některých případech potřebují s využitím JNI vytvářet javovské objekty, popř. volat metody již vytvořených javovských objektů.

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ů

11. Odkazy na Internetu

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());

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:

zabbix_tip

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.or­g/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:

11. Odkazy na Internetu

  1. Breakpoint (Wikipedia)
    http://cs.wikipedia.org/wi­ki/Breakpoint
  2. JVM Tool Interface Version 1.2 Documentation
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  3. JVM Tool Interface Version 1.2 Documentation – SetBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#SetBreakpoint
  4. JVM Tool Interface Version 1.2 Documentation – ClearBreakpoint
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#ClearBreakpoint
  5. JVM Tool Interface Version 1.2 Documentation – Breakpoint (callback funkce)
    http://docs.oracle.com/ja­vase/6/docs/platform/jvmti/jvmti­.html#Breakpoint
  6. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  7. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  8. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  9. IBM JVMTI extensions
    http://publib.boulder.ibm­.com/infocenter/realtime/v2r0/in­dex.jsp?topic=%2Fcom.ibm.sof­trt.doc%2Fdiag%2Ftools%2Fjvmti_ex­tensions.html
  10. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  11. Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
    http://www.mobilefish.com/tu­torials/java/java_quickgu­ide_jvm_instruction_set.html
  12. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  13. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  14. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  15. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  16. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  17. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  18. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  19. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.