Pohled pod kapotu JVM - sledování čtení i zápisu do vybraného atributu třídy či atributu objektu

Pavel Tišnovský 29. 1. 2013

V dnešním článku o jazyce Java a vlastnostech JVM si ukážeme, jak může být použit JVM TI agent pro sledování čtení či zápisu do atributu třídy či objektu. Rozhraní JVM TI nám totiž dává k dispozici callback funkce zavolané ve chvíli, kdy libovolné vlákno k označeným (vybraným) atributům přistupuje.

Obsah

1. Callback funkce zavolaná při načítání třídy do virtuálního stroje Javy

2. Callback funkce zavolaná po inicializaci třídy

3. Demonstrační agent číslo 27 – výpis všech načítaných a inicializovaných tříd

4. Výsledek běhu demonstračního agenta číslo 27

5. Sledování přístupu (čtení, zápis) k vybranému atributu

6. Nastavení požadovaných vlastností JVM TI agenta

7. Nastavení režimů notifikace a registrace callback funkcí

8. Demonstrační agent číslo 28 – jednoduchá detekce přístupu k atributu třídy Test28

9. Výsledek běhu demonstračního agenta číslo 28

10. Zdrojové kódy obou demonstračních agentů a k nim příslušných testovacích příkladů

11. Odkazy na Internetu

1. Callback funkce zavolaná při načítání třídy do virtuálního stroje Javy

V předchozí části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje Javy jsme si vysvětlili činnost demonstračního JVM TI agenta, který pro vybranou třídu dokázal vypsat všechny atributy, včetně jejich typu a případných modifikátorů. Dnes postoupíme poněkud dále, protože si ukážeme, jak je možné sledovat všechny operace čtení a především pak zápisu do vybraného atributu, ať již se jedná o třídní atribut (statický) či o atribut objektu (nestatický). Nejprve si však musíme popsat ještě jednu funkcionalitu nabízenou rozhraním JVM TI, která se týká možností sledování načítání a inicializace tříd ve virtuálním stroji Javy. Jeden z problémů se sledováním přístupu k atributům totiž spočívá v tom, kdy přesně je možné zaregistrovat callback funkce zavolané ve chvíli, kdy je atribut čten nebo je do něj zapisována nová hodnota.

Nejjednodušší je provést registraci ve chvíli, kdy je příslušná třída (jejíž atribut/atributy se mají sledovat) načtena do virtuálního stroje Javy. Ve skutečnosti se jedná o dvoufázovou činnost, alespoň z hlediska JVM TI agenta. Můžeme zachytit okamžik, kdy je třída skutečně teprve načítána, ale ještě se neprovádí její inicializace. V tomto okamžiku je například možné provést takzvanou retransformaci třídy, tj. například změnu bajtkódu jednotlivých metod atd. Retransformace je poměrně často používána například tehdy, pokud je zapotřebí sledovat vstupy a výstupy z vybraných metod, protože použití obecných sledovacích nástrojů založených na callback funkcích MethodEntry() a MethodExit() vede k velké degradaci výpočetního výkonu celého sledovaného virtuálního stroje Javy. Vraťme se však k okamžiku, kdy je třída načítána do virtuálního stroje Javy. Tento okamžik je možné pro každou načítanou třídu jednoduše detekovat; postačuje pouze nastavit vhodný režim notifikace:

    /* Udalost pri nacitani tridy. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_CLASS_LOAD)) != JNI_OK)
    {
        return error_code;
    }

A následně zaregistrovat callback funkci ClassLoad()v datové struktuře jvmtiEventCallbacks. Volaná callback funkce musí mít následující hlavičku:

/*
 * Callback funkce zavolana pri nacitani tridy do virtualniho stroje.
 */
static void JNICALL callback_on_class_load(
        jvmtiEnv *jvmti_env,
        JNIEnv   *jni_env,
        jthread   thread,
        jclass    class)

Poznámka: pro sledování načítání tříd do JVM není zapotřebí nastavovat žádné speciální schopnosti JVM TI agenta, tj. nemusí se měnit obsah struktury jvmtiCapabilities.

2. Callback funkce zavolaná po inicializaci třídy

Callback funkce callback_on_class_load() popsaná v předchozí kapitole je volána ještě předtím, než je třída korektně inicializována, zpracována a uložena do příslušné paměťové oblasti virtuálního stroje Javy. To s sebou přináší klady, ale samozřejmě i zápory. Mezi klady patří již zmíněná možnost retransformace třídy či libovolné úpravy třídy před jejím dalším standardním zpracováním. Mezi zápory pak patří především fakt, že se o třídě v daném okamžiku dá získat jen velmi málo informací standardními prostředky – můžeme například získat signaturu třídy, ale již ne její metody a atributy. Z tohoto důvodu by pro nás bylo vhodnější zachytit spíše okamžik, kdy je již třída korektně inicializována a kdy již můžeme získat seznam jejich atributů. I tuto funkcionalitu nám rozhraní JVM TI samozřejmě nabízí; konkrétně se jedná o callback funkci pojmenovanou v datové struktuře jvmtiEventCallbacks ClassPrepare(). Zajímavé je, že tato callback funkce má zcela stejnou hlavičku, jako callback funkce ClassLoad():

/*
 * 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)
{
}

Ani pro zavolání této callback funkce není zapotřebí nastavovat žádnou speciální schopnost JVM TI agenta, tj. nemusí se měnit obsah struktury jvmtiCapabilities.

3. Demonstrační agent číslo 27 – výpis všech načítaných a inicializovaných tříd

Obě události popsané v předchozích dvou kapitolách budou využity v dnešním prvním demonstračním JVM TI agentovi, který na standardní výstup postupně vypíše všechny třídy načítané do virtuálního stroje Javy i třídy skutečně inicializované (oba dva seznamy tříd by samozřejmě měly být shodné). V dalších odstavcích budou popsány nejzajímavější funkce implementované v JVM TI agentovi.

Nejprve je nutné nastavit nové režimy notifikace, což se provádí ve funkci set_event_notification_modes(). V agentovi budou zachytávány čtyři typy událostí – inicializace virtuálního stroje Javy, ukončení práce virtuálního stroje Javy, načtení třídy a inicializace třídy – z nichž každá má přiřazen vlastní režim notifikace:

/*
 * Nastaveni udalosti, pro nez se maji zavolat callback funkce.
 */
jvmtiError set_event_notification_modes(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    /* Potrebujeme zachytavat udalost inicializace virtualniho stroje. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_VM_INIT)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Potrebujeme zachytavat udalost ukonceni prace virtualniho stroje. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_VM_DEATH)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Udalost pri nacitani tridy. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_CLASS_LOAD)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Udalost pri priprave tridy ve virtualnim stroji. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_CLASS_PREPARE)) != JNI_OK)
    {
        return error_code;
    }
 
    return error_code;
}

Dále je nutné v datové struktuře jvmtiEventCallbacks nastavit ukazatele na všechny čtyři callback funkce (inicializace JVM, ukončení práce JVM, načtení třídy, inicializace třídy). To se provede ve funkci register_all_callback_functions():

/*
 * Registrace ctyr callback funkci zavolanych virtualnim strojem Javy.
 */
jvmtiError register_all_callback_functions(jvmtiEnv *jvmti)
{
    jvmtiEventCallbacks callbacks;
    jvmtiError error_code;
 
    memset(&callbacks, 0, sizeof(callbacks));
 
    /* JVMTI_EVENT_VM_INIT */
    callbacks.VMInit = &callback_on_vm_init;
 
    /* JVMTI_EVENT_VM_DEATH */
    callbacks.VMDeath = &callback_on_vm_death;
 
    /* JVMTI_EVENT_CLASS_LOAD */
    callbacks.ClassLoad = &callback_on_class_load;
 
    /* JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE */
    callbacks.ClassPrepare = &callback_on_class_prepare;
 
    error_code = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
    check_jvmti_error(jvmti, error_code, "Cannot set JVM TI callbacks");
    return error_code;
}

Callback funkce zavolaná při načtení každé třídy je interně velmi jednoduchá – pouze se v kritické sekci zavolá uživatelská funkce print_class_name():

/*
 * Callback funkce zavolana pri nacitani tridy do virtualniho stroje.
 */
static void JNICALL callback_on_class_load(
        jvmtiEnv *jvmti_env,
        JNIEnv   *jni_env,
        jthread   thread,
        jclass    class)
{
    enter_critical_section(jvmti_env);
    printf("Class load:   ");
    print_class_name(jvmti_env, class);
    exit_critical_section(jvmti_env);
}

To stejné platí pro callback funkci zavolanou ve chvíli inicializace třídy:

/*
 * 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)
{
    enter_critical_section(jvmti_env);
    printf("Class prepare:");
    print_class_name(jvmti_env, class);
    exit_critical_section(jvmti_env);
}

Pomocná uživatelská funkce print_class_name() získá signaturu třídy (ta existuje v obou případech, tj. již při načtení třídy) a následně tuto signaturu upraví do čitelnějšího tvaru, který je následně vytištěn na standardní výstup:

/*
 * Vypise jmeno tridy pro zadany jclass
 */
void print_class_name(jvmtiEnv *jvmti_env, jclass class)
{
    jvmtiError error;
    char *class_name_ptr;
    char *updated_class_name_ptr;
 
    /* 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, ';');
 
    /* tisk upraveneho jmena tridy */
    puts(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");
}

4. Výsledek běhu demonstračního agenta číslo 27

Demonstrační agent popsaný výše se přeloží stejným způsobem, jako již dříve popsaní agenti:

gcc -Wall -ansi -I/usr/lib/jvm/java-1.6.0-openjdk/include/ -shared -o libagent27.so agent27.c

Spuštění virtuálního stroje Javy společně s JVM TI agentem je zajištěno příkazem:

java -agentpath:./libagent27.so Test27 2> /dev/null

Po spuštění agent na standardní výstup vypíše všechny načítané i inicializované třídy. Ve výpisu je zvýrazněna testovací třída Test27, která je samozřejmě taktéž načtena, protože až poté může být nalezena a spuštěna její metoda public static void main(String[] args):

Agent27: Agent_OnLoad
Agent27: JVM TI version is correct
Agent27: Got VM init event
Class load:   java.lang.System;
Class load:   java.nio.charset.Charset;
Class load:   java.lang.String;
Class prepare:java.lang.ClassNotFoundException;
Class load:   java.net.URLClassLoader$1;
Class prepare:java.net.URLClassLoader$1;
Class load:   sun.misc.URLClassPath$3;
Class prepare:sun.misc.URLClassPath$3;
Class load:   sun.misc.URLClassPath$Loader;
Class load:   sun.misc.URLClassPath$JarLoader;
Class prepare:sun.misc.URLClassPath$Loader;
Class prepare:sun.misc.URLClassPath$JarLoader;
Class prepare:java.lang.StringBuffer;
Class prepare:java.lang.Short;
Class load:   sun.misc.URLClassPath$JarLoader$1;
Class prepare:sun.misc.URLClassPath$JarLoader$1;
Class load:   sun.misc.FileURLMapper;
Class prepare:sun.misc.FileURLMapper;
Class load:   java.util.zip.ZipConstants;
Class load:   java.util.zip.ZipFile;
Class load:   java.util.jar.JarFile;
Class prepare:java.util.zip.ZipConstants;
Class prepare:java.util.zip.ZipFile;
Class prepare:java.util.jar.JarFile;
Class load:   sun.misc.JavaUtilJarAccess;
Class load:   java.util.jar.JavaUtilJarAccessImpl;
Class prepare:sun.misc.JavaUtilJarAccess;
Class prepare:java.util.jar.JavaUtilJarAccessImpl;
Class load:   sun.misc.JarIndex;
Class prepare:sun.misc.JarIndex;
Class load:   sun.misc.ExtensionDependency;
Class prepare:sun.misc.ExtensionDependency;
Class load:   java.util.zip.ZipEntry;
Class prepare:java.util.zip.ZipEntry;
Class load:   java.util.jar.JarEntry;
Class load:   java.util.jar.JarFile$JarFileEntry;
Class prepare:java.util.jar.JarEntry;
Class prepare:java.util.jar.JarFile$JarFileEntry;
Class load:   java.io.DataInput;
Class load:   java.io.DataInputStream;
Class prepare:java.io.DataInput;
Class prepare:java.io.DataInputStream;
Class load:   java.util.zip.ZipFile$ZipFileInputStream;
Class prepare:java.util.zip.ZipFile$ZipFileInputStream;
Class load:   java.security.PrivilegedActionException;
Class prepare:java.security.PrivilegedActionException;
Class load:   sun.misc.URLClassPath$FileLoader;
Class prepare:sun.misc.URLClassPath$FileLoader;
Class load:   sun.misc.Resource;
Class load:   sun.misc.URLClassPath$FileLoader$1;
Class prepare:sun.misc.Resource;
Class prepare:sun.misc.URLClassPath$FileLoader$1;
Class load:   sun.nio.ByteBuffered;
Class load:   java.security.CodeSource;
Class prepare:java.security.CodeSource;
Class load:   java.security.PermissionCollection;
Class load:   java.security.Permissions;
Class prepare:java.security.PermissionCollection;
Class prepare:java.security.Permissions;
Class load:   java.net.URLConnection;
Class load:   sun.net.www.URLConnection;
Class load:   sun.net.www.protocol.file.FileURLConnection;
Class prepare:java.net.URLConnection;
Class prepare:sun.net.www.URLConnection;
Class prepare:sun.net.www.protocol.file.FileURLConnection;
Class load:   java.net.ContentHandler;
Class load:   java.net.UnknownContentHandler;
Class prepare:java.net.ContentHandler;
Class prepare:java.net.UnknownContentHandler;
Class load:   sun.net.www.MessageHeader;
Class prepare:sun.net.www.MessageHeader;
Class load:   java.io.FilePermission;
Class prepare:java.io.FilePermission;
Class load:   java.io.FilePermission$1;
Class prepare:java.io.FilePermission$1;
Class load:   java.security.Policy;
Class load:   sun.security.provider.PolicyFile;
Class prepare:java.security.Policy;
Class prepare:sun.security.provider.PolicyFile;
Class load:   java.security.Policy$UnsupportedEmptyCollection;
Class prepare:java.security.Policy$UnsupportedEmptyCollection;
Class load:   java.io.FilePermissionCollection;
Class prepare:java.io.FilePermissionCollection;
Class load:   java.security.AllPermission;
Class load:   java.security.UnresolvedPermission;
Class load:   java.security.BasicPermissionCollection;
Class prepare:java.security.BasicPermissionCollection;
Class prepare:java.security.ProtectionDomain;
Class load:   sun.misc.JavaSecurityProtectionDomainAccess;
Class load:   java.security.ProtectionDomain$2;
Class prepare:sun.misc.JavaSecurityProtectionDomainAccess;
Class prepare:java.security.ProtectionDomain$2;
Class load:   java.security.ProtectionDomain$Key;
Class prepare:java.security.ProtectionDomain$Key;
Class load:   java.security.Principal;
Class load:   java.security.cert.Certificate;
Class load:   java.lang.Object;
Class load:   Test27;
Class prepare:Test27;
Class load:   java.util.ArrayList$Itr;
Class prepare:java.util.ArrayList$Itr;
Class load:   java.util.IdentityHashMap$KeySet;
Class prepare:java.util.IdentityHashMap$KeySet;
Class load:   java.util.IdentityHashMap$IdentityHashMapIterator;
Class load:   java.util.IdentityHashMap$KeyIterator;
Class prepare:java.util.IdentityHashMap$IdentityHashMapIterator;
Class prepare:java.util.IdentityHashMap$KeyIterator;
Class load:   java.io.DeleteOnExitHook;
Class prepare:java.io.DeleteOnExitHook;
Class load:   java.util.LinkedHashSet;
Class prepare:java.util.LinkedHashSet;
Class load:   java.util.HashMap$KeySet;
Class prepare:java.util.HashMap$KeySet;
Class load:   java.util.LinkedHashMap$LinkedHashIterator;
Class load:   java.util.LinkedHashMap$KeyIterator;
Class prepare:java.util.LinkedHashMap$LinkedHashIterator;
Class prepare:java.util.LinkedHashMap$KeyIterator;
Class load:   java.util.Collections;
Class prepare:java.util.Collections;
Class load:   java.util.Collections$EmptySet;
Class prepare:java.util.Collections$EmptySet;
Class load:   java.util.Collections$EmptyList;
Class prepare:java.util.Collections$EmptyList;
Class load:   java.util.Collections$EmptyMap;
Class prepare:java.util.Collections$EmptyMap;
Agent27: Got VM Death event
Agent27: Agent_OnUnload

5. Sledování přístupu (čtení, zápis) k vybranému atributu

V dalším demonstračním JVM TI agentovi, jehož konstrukci si postupně popíšeme v následujících čtyřech kapitolách, budeme chtít vypsat všechny operace čtení a zápisu do atributu i definovaného ve třídě Test28. Jedná se o nestatický atribut, což znamená, že v paměti bude uložen společně s každou instancí třídy Test28. K atributu se přistupuje především v metodě run(), kde je na některých řádcích vidět, že se provádí současné čtení staré hodnoty i zápis hodnoty nové:

/**
  * Testovaci trida pouzita pro test dvacateho
  * osmeho demonstracniho JVM TI agenta.
  */
public class Test28 {
    int i;
 
    public void run() {
        i = 10;
        i = i+1;
        i--;
        i *= 2;
        ++i;
    }
 
    /**
      * Spusteni testu.
      */
    public static void main(String[] args) {
        new Test28().run();
    }
}

(čtenářům tohoto seriálu asi nemá smysl popisovat, že příkaz ++i; znamená přečtení a současně i zápis nové hodnoty do atributu i)

6. Nastavení požadovaných vlastností JVM TI agenta

První věcí, kterou musíme ve vytvářeném demonstračním JVM TI agentovi implementovat, je nastavení nových požadovaných schopností agenta. Aby bylo možné detekovat čtení i zápis do libovolného atributu, musí být v datové struktuře jvmtiCapabilities nastaveny položky can_generate_field_access_events (čtení) a can_generate_field_modifi­cation_events (modifikace=zápis). Toto nastavení se provede v uživatelské funkci set_capabilities(), samozřejmě s testem, zda daný virtuální stroj Javy tuto funkcionalitu skutečně podporuje:

/*
 * Nastaveni pozadovanych schopnosti agenta.
 */
jvmtiError set_capabilities(jvmtiEnv *jvmti)
{
    jvmtiCapabilities capabilities;
    jvmtiError error_code;
 
    memset(&capabilities, 0, sizeof(jvmtiCapabilities));
 
    /* vyuzivame dve specialni schopnosti agenta */
    capabilities.can_generate_field_access_events = 1;
    capabilities.can_generate_field_modification_events = 1;
 
    error_code = (*jvmti)->AddCapabilities(jvmti, &capabilities);
    check_jvmti_error(jvmti, error_code, "Unable to get necessary JVMTI capabilities.");
    return error_code;
}

7. Nastavení režimů notifikace a registrace callback funkcí

Dále je nutné vhodně nastavit režimy notifikace, což se v našich demonstračních JVM TI agentech již tradičně provádí v uživatelské funkci set_event_notification_modes(). Ke dvěma již existujícím režimům notifikace je přidána další dvojice – JVMTI_EVENT_FIELD_ACCESS a JVMTI_EVENT_FIELD_MODIFICATION:

/*
 * Nastaveni udalosti, pro nez se maji zavolat callback funkce.
 */
jvmtiError set_event_notification_modes(jvmtiEnv *jvmti)
{
    jvmtiError error_code;
 
    /* Potrebujeme zachytavat udalost inicializace virtualniho stroje. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_VM_INIT)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Potrebujeme zachytavat udalost ukonceni prace virtualniho stroje. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_VM_DEATH)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Udalost pri priprave tridy ve virtualnim stroji. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_CLASS_PREPARE)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Udalost pri pristupu k atributu. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_FIELD_ACCESS)) != JNI_OK)
    {
        return error_code;
    }
 
    /* Udalost pri modifikaci atributu. */
    if ((error_code = set_event_notification_mode(jvmti, JVMTI_EVENT_FIELD_MODIFICATION)) != JNI_OK)
    {
        return error_code;
    }
 
    return error_code;
}

Posléze musíme zaregistrovat celkem pět callback funkcí. Ke dvěma známým callback funkcím (inicializace a konec práce JVM) se přidává callback funkce zavolaná po instalaci každé třídy (ClassPrepare), callback funkce zavolaná při přístupu k atributu (FieldAccess) a konečně callback funkce zavolaná při změně hodnoty atributu (FieldModification):

/*
 * Registrace peti callback funkci zavolanych virtualnim strojem javy.
 */
jvmtiError register_all_callback_functions(jvmtiEnv *jvmti)
{
    jvmtiEventCallbacks callbacks;
    jvmtiError error_code;
 
    memset(&callbacks, 0, sizeof(callbacks));
 
    /* JVMTI_EVENT_VM_INIT */
    callbacks.VMInit = &callback_on_vm_init;
 
    /* JVMTI_EVENT_VM_DEATH */
    callbacks.VMDeath = &callback_on_vm_death;
 
    /* JVMTI_EVENT_CLASS_PREPARE */
    callbacks.ClassPrepare = &callback_on_class_prepare;
 
    /* JVMTI_EVENT_FIELD_ACCESS */
    callbacks.FieldAccess = &callback_on_field_access;
 
    /* JVMTI_EVENT_FIELD_MODIFICATION */
    callbacks.FieldModification = &callback_on_field_modification;
 
    error_code = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
    check_jvmti_error(jvmti, error_code, "Cannot set JVM TI callbacks");
    return error_code;
}

8. Demonstrační agent číslo 28 – jednoduchá detekce přístupu k atributu třídy Test28

Callback funkce zavolaná při přístupu k označenému atributu je prozatím velmi jednoduchá, protože obsahuje pouze hlášení vypisované na standardní výstup. Vylepšení této funkce si ukážeme až příště:

/*
 * Callback funkce zavolana pri pristupu (cteni) vybranych atributu
 */
static void JNICALL callback_on_field_access(
        jvmtiEnv  *jvmti_env,
        JNIEnv    *jni_env,
        jthread    thread,
        jmethodID  method,
        jlocation  location,
        jclass     class,
        jobject    object,
        jfieldID   field)
{
    /* prozatim bude telo monitoru takto jednoduche */
    MSG("Field access");
}

Callback funkce volaná při změně hodnoty atributu má prvních osm parametrů shodných z předchozí callback funkcí callback_on_field_access(), ovšem přidávají se dva nové parametry. Především poslední parametr je zajímavý, protože nese informaci o nové hodnotě, která se má do atributu přiřadit. My si práci poněkud zjednodušíme, protože víme, že monitorovaný atribut i je typu int, tudíž přímo, bez dalších zkoumání budeme vypisovat hodnotu uloženou v jvalue.i:

/*
 * Callback funkce zavolana pri zapisu vybranych atributu
 */
static void JNICALL callback_on_field_modification(
        jvmtiEnv *jvmti_env,
        JNIEnv   *jni_env,
        jthread   thread,
        jmethodID method,
        jlocation location,
        jclass    class,
        jobject   object,
        jfieldID  field,
        char signature_type,
        jvalue new_value)
{
    /* prozatim vypiseme pouze novou hodnotu zapisovanou do atributu */
    int value = (int)new_value.i;
    printf("i := %d\n", value);
}

Aby se tyto dvě callback funkce skutečně zavolaly při každém přístupu k atributu Test28.i, musíme pro tento atribut callback funkce zaregistrovat s využitím JVM TI funkcí SetFieldAccessWatch() a SetFieldModificationWatch(). Uděláme to prozatím velmi primitivním způsobem – ve chvíli, kdy je načítána třída se jménem Test28 se pro jistotu pro všechny její atributy (bude ovšem existovat je jeden) tyto dvě registrační funkce zavolají. Podívejte se, jak je to uděláno:

/*
 * 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, ';');
 
    /* pokud jsme nasli to pravou tridu */
    if (strcmp(updated_class_name_ptr, TEST_CLASS_NAME) == 0)
    {
        puts("Class "TEST_CLASS_NAME" prepared, setting field monitor");
        prepare_field_monitor(jvmti_env, class);
    }
 
    /* 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);
}

Při pohledu na funkci callback_on_class_prepare() je patrné, že ve chvíli, kdy je načítána třída, jejíž jméno je uloženo v symbolické konstantě TEST_CLASS_NAME, je pro tuto třídu zavolána funkce prepare_field_monitor():

/*
 * Registrace monitoru pro vsechny atributy vybrane tridy.
 */
void prepare_field_monitor(jvmtiEnv *jvmti_env, jclass class)
{
    jvmtiError error;
    int        field_count;
    jfieldID  *fields_array;
 
    /* precist vsechny atributy tridy */
    error = (*jvmti_env)->GetClassFields(jvmti_env, class, &field_count, &fields_array);
    check_jvmti_error(jvmti_env, error, "get class fields");
 
    /* pole atributu bylo inicializovano */
    if (error == JVMTI_ERROR_NONE)
    {
        int i;
        /* projit vsemi atributy a nastavit monitory */
        for (i = 0; i < field_count; i++)
        {
            jfieldID field = fields_array[i];
            printf("Setting monitors for field #%ld\n", (long)field);
 
            error = (*jvmti_env)->SetFieldModificationWatch(jvmti_env, class, field);
            check_jvmti_error(jvmti_env, error, "SetFieldModificationWatch");
 
            error = (*jvmti_env)->SetFieldAccessWatch(jvmti_env, class, field);
            check_jvmti_error(jvmti_env, error, "SetFieldAccessWatch");
        }
    }
 
    /* dealokace pole ziskaneho pres GetClassFields() */
    error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)fields_array);
    check_jvmti_error(jvmti_env, error, "deallocate class fields array");
}

Ve funkci prepare_field_monitor() se prochází všechny atributy třídy stejným způsobem, jaký již známe z minula.

9. Výsledek běhu demonstračního agenta číslo 28

Demonstrační agent popsaný v předchozích čtyřech kapitolách se přeloží stejným způsobem, jako již dříve popsaní agenti:

gcc -Wall -ansi -I/usr/lib/jvm/java-1.6.0-openjdk/include/ -shared -o libagent28.so agent28.c

Spuštění virtuálního stroje Javy společně s JVM TI agentem je zajištěno příkazem:

java -agentpath:./libagent28.so Test28 2> /dev/null

JVM TI agent po načtení, inicializaci a spuštění testovací javovské třídy Test28 vypíše na standardní výstup následující sekvenci zpráv, z nichž je patrné, že celkem čtyřikrát došlo k přečtení hodnoty atributu (i) a pětkrát k zápisu nové hodnoty:

widgety

Agent28: Agent_OnLoad
Agent28: JVM TI version is correct
Agent28: Got VM init event
Class Test28; prepared, setting field monitor
Setting monitors for field #34
i := 10
Agent28: Field access
i := 11
Agent28: Field access
i := 10
Agent28: Field access
i := 20
Agent28: Field access
i := 21
Agent28: Got VM Death event
Agent28: Agent_OnUnload

Tento výpis je vhodné porovnat se zdrojovým kódem testovací javovské třídy Test28, který byl uveden v páté kapitole.

10. Zdrojové kódy obou demonstračních agentů a k nim příslušných testovacích příkladů

Podobně jako v mnoha předcházejících částech tohoto seriálu byly i dnešní dva demonstrační JVM TI agenti kvůli snazšímu udržování všech zdrojových kódů uloženi do Mercurial repositáře, který je dostupný na adrese http://icedtea.classpath.or­g/people/ptisnovs/jvm-tools/. Prozatím nejnovější verze dnes popisovaných JVM TI agentů i dalších potřebných skriptů a testovacích javovských tříd jsou dostupné na následujících adresách:

11. Odkazy na Internetu

  1. The JVM Tool Interface (JVM TI): How VM Agents Work
    http://www.oracle.com/technet­work/articles/javase/jvm-ti-141370.html
  2. JVM Tool Interface Version 1.2
    http://docs.oracle.com/ja­vase/7/docs/platform/jvmti/jvmti­.html
  3. Creating a Debugging and Profiling Agent with JVMTI
    http://www.oracle.com/technet­work/articles/javase/jvmti-136367.html
  4. JVM TI (Wikipedia)
    http://en.wikipedia.org/wiki/JVM_TI
  5. 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
  6. An empirical study of Java bytecode programs
    http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/
  7. 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
  8. The JVM Instruction Set
    http://mpdeboer.home.xs4a­ll.nl/scriptie/node14.html
  9. Control Flow in the Java Virtual Machine
    http://www.artima.com/under­thehood/flowP.html
  10. The class File Format
    http://java.sun.com/docs/bo­oks/jvms/second_edition/html/Clas­sFile.doc.html
  11. javap – The Java Class File Disassembler
    http://docs.oracle.com/ja­vase/1.4.2/docs/tooldocs/win­dows/javap.html
  12. javap-java-1.6.0-openjdk(1) – Linux man page
    http://linux.die.net/man/1/javap-java-1.6.0-openjdk
  13. Using javap
    http://www.idevelopment.in­fo/data/Programming/java/mis­cellaneous_java/Using_javap­.html
  14. Examine class files with the javap command
    http://www.techrepublic.com/ar­ticle/examine-class-files-with-the-javap-command/5815354
  15. Bytecode Engineering
    http://book.chinaunix.net/spe­cial/ebook/Core_Java2_Volu­me2AF/0131118269/ch13lev1sec6­.html
Našli jste v článku chybu?
Vitalia.cz: Inspekce našla nelegální sklad v SAPĚ. Zase

Inspekce našla nelegální sklad v SAPĚ. Zase

DigiZone.cz: Nova opět stahuje „milionáře“

Nova opět stahuje „milionáře“

Vitalia.cz: Jaký je rozdíl mezi brambůrky a chipsy?

Jaký je rozdíl mezi brambůrky a chipsy?

DigiZone.cz: Rapl: seriál, který vás smíří s ČT

Rapl: seriál, který vás smíří s ČT

DigiZone.cz: Test LG 55UH750V aneb Cena/výkon

Test LG 55UH750V aneb Cena/výkon

Vitalia.cz: Tahák, jak vyzrát nad zápachem z úst

Tahák, jak vyzrát nad zápachem z úst

120na80.cz: Co je padesátkrát sladší než cukr?

Co je padesátkrát sladší než cukr?

Podnikatel.cz: Znáte už 5 novinek k #EET

Znáte už 5 novinek k #EET

Podnikatel.cz: Dva měsíce na EET. Budou stačit?

Dva měsíce na EET. Budou stačit?

Vitalia.cz: Kterou dýni můžete jíst za syrova?

Kterou dýni můžete jíst za syrova?

Podnikatel.cz: Babišovi se nedá věřit, stěžovali si hospodští

Babišovi se nedá věřit, stěžovali si hospodští

Vitalia.cz: dTest odhalil ten nejlepší kečup

dTest odhalil ten nejlepší kečup

Vitalia.cz: Jsou vegani a vyrábějí nemléko

Jsou vegani a vyrábějí nemléko

120na80.cz: Hrbatá prsa aneb mýty o implantátech

Hrbatá prsa aneb mýty o implantátech

DigiZone.cz: Světový pohár v přímém přenosu na ČT

Světový pohár v přímém přenosu na ČT

DigiZone.cz: Mordparta: trochu podchlazený 87. revír

Mordparta: trochu podchlazený 87. revír

Vitalia.cz: Tohle jsou nejlepší česká piva podle odborníků

Tohle jsou nejlepší česká piva podle odborníků

Podnikatel.cz: Takhle se prodávají mražené potraviny

Takhle se prodávají mražené potraviny

Vitalia.cz: Test dětských svačinek: Tyhle ne!

Test dětských svačinek: Tyhle ne!

DigiZone.cz: Funbox 4K v DVB-T2 má ostrý provoz

Funbox 4K v DVB-T2 má ostrý provoz