Hlavní navigace

Pohled pod kapotu JVM - průchod objekty uloženými na haldě (heapu)

Pavel Tišnovský 18. 12. 2012

V dnešní části seriálu o jazyce Java i o vlastnostech JVM se seznámíme se způsobem práce s objekty uloženými na haldě s využitím rozhraní JVM TI. Agenti připojení k virtuálnímu stroji Javy přes JVM TI totiž poměrně často potřebují nějaké údaje (instance určitých tříd) z haldy přečíst a následně zpracovat.

Obsah

1. Pohled pod kapotu JVM – průchod objekty uloženými na haldě (heapu)

2. Callback funkce volané při průchodu haldou

3. Osmnáctý demonstrační příklad – průchod všemi objekty uloženými na haldě

4. Uživatelská data přenášená do callback funkce při průchodu objekty uloženými na hladě

5. Devatenáctý demonstrační příklad – výpis souhrnných informací o obsazení haldy (počet objektů a jejich velikost)

6. Přidání tagů ke třídám

7. Použití tagů při přiřazování objektů k jejich třídám

8. Dvacátý demonstrační příklad – výpis všech objektů na haldě včetně jména příslušné třídy

9. Zdrojové kódy všech tří demonstračních příkladů

10. Odkazy na Internetu

1. Pohled pod kapotu JVM – průchod objekty uloženými na haldě (heapu)

V dnešní – již 59.části – seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje Javy si na třech demonstračních příkladech ukážeme, jakým způsobem je možné s využitím agentů komunikujících s JVM přes rozhraní JVM TI procházet všemi objekty uloženými na haldě (heap) a získat i další informace o těchto objektech, především pak třídu, z níž je objekt uložený na haldě vytvořen. V rozhraní JVM TI je možné pro zahájení průchodu všemi objekty na haldě využít funkci nazvanou IterateThroughHeap(). Při volání této funkce se jí mj. předá i datová struktura obsahující ukazatele na callback funkce volané pro jednotlivé objekty nalezené na haldě; v současné verzi rozhraní JVM TI se jedná o pětici callback funkcí, zbytek datové struktury není využit.

Důležité je, že haldou (heapem) je možné procházet nepřímo právě s využitím IterateThroughHeap() a na ni navázaných callback funkcí, protože již minule jsme si řekli, že v rozhraní JVM TI nejsou k dispozici žádné funkce, které by dokázaly přímo vrátit seznam objektů vyfiltrovaný na základě nějaké podmínky. Pokud tedy programátor potřebuje zjistit informace o určitých typech objektů na haldě, musí si sám implementovat a následně zaregistrovat příslušné callback funkce, kterým se budou tyto informace předávat. Hlavička funkce IterateThroughHeap() vypadá následovně:

jvmtiError IterateThroughHeap(
            jvmtiEnv* env,
            jint      heap_filter,
            jclass    klass,
            const jvmtiHeapCallbacks* callbacks,
            const void* user_data)

Význam prvního parametru této funkce je zřejmý, protože je v něm přenesena (přesněji řečeno pouze odkázána) struktura obsahující aktuální prostředí JVM TI. Zajímavější je druhý parametr, přes který je možné specifikovat základní filtr, který určuje, pro které typy objektů se budou callback funkce volat. Využít je možné následující hodnoty, popř. jejich bitové kombinace spojené přes operátor + nebo |:

Konstanta Hodnota Význam
JVMTI_HEAP_FILTER_TAGGED 0×04 objekty s přiřazeným tagem jsou odfiltrovány
JVMTI_HEAP_FILTER_UNTAGGED 0×08 objekty bez tagu jsou odfiltrovány
JVMTI_HEAP_FILTER_CLASS_TAGGED 0×10 objekty, jejichž třídy mají přiřazeny tag jsou odfiltrovány
JVMTI_HEAP_FILTER_CLASS_UNTAGGED 0×20 přesný opak předchozí konstanty

Označením tag je zde myšlen celočíselný údaj rozdílný od nuly, kterým mohou být objekty „označkovány“, a to opět pomocí rozhraní JVM TI. Tagy mají při procházení haldou poměrně velký význam a seznámíme se s nimi v šesté kapitole.

Ve třetím parametru lze předat buď hodnotu NULL popř. identifikátor třídy, který je opět použit pro filtraci. To například znamená, že pokud nás zajímají informace pouze o instancích třídy org.foo.bar.Baz, lze toho velmi jednoduše docílit, aniž by se musela procházet celá halda, resp.  aniž by byla příslušná callback funkce volána pro všechny instance ostatních tříd.

Čtvrtým parametrem je ukazatel na datovou strukturu nazvanou jvmtiHeapCallbacks. Právě do této struktury může programátor uložit odkazy na callback funkce. Struktura sice teoreticky pojme šestnáct ukazatelů na callback funkce, ovšem využito je v současnosti jen pět prvních ukazatelů a i libovolný z těchto ukazatelů může mít hodnotu NULL:

typedef struct {
    jvmtiHeapIterationCallback        heap_iteration_callback;
    jvmtiHeapReferenceCallback        heap_reference_callback;
    jvmtiPrimitiveFieldCallback       primitive_field_callback;
    jvmtiArrayPrimitiveValueCallback  array_primitive_value_callback;
    jvmtiStringPrimitiveValueCallback string_primitive_value_callback;
    jvmtiReservedCallback             reserved5;
    jvmtiReservedCallback             reserved6;
    jvmtiReservedCallback             reserved7;
    jvmtiReservedCallback             reserved8;
    jvmtiReservedCallback             reserved9;
    jvmtiReservedCallback             reserved10;
    jvmtiReservedCallback             reserved11;
    jvmtiReservedCallback             reserved12;
    jvmtiReservedCallback             reserved13;
    jvmtiReservedCallback             reserved14;
    jvmtiReservedCallback             reserved15;
} jvmtiHeapCallbacks;

Význam pěti ukazatelů na callback funkce je následující:

# Typ Název Význam
1 jvmtiHeapIterationCallback heap_iteration_callback voláno pro běžné objekty
2 jvmtiHeapReferenceCallback heap_reference_callback voláni pro reference na objekty
3 jvmtiPrimitiveFieldCallback primitive_field_callback voláno pro atributy primitivního datového typu
4 jvmtiArrayPrimitiveValueCallback array_primitive_value_callback voláno pro pole primitivních datových typů
5 jvmtiStringPrimitiveValueCallback string_primitive_value_callback voláno pro řetězce

V pátém parametru se předává buď hodnota NULL nebo ukazatel na uživatelská data, například na pole či na nějakou datovou strukturu. Tato uživatelská data jsou při volání zaregistrovaných callback funkcí těmto funkcím jednoduše předána. Příklad použití uživatelských dat si ukážeme ve čtvrté a v páté kapitole.

2. Callback funkce volané při průchodu haldou

V datové struktuře jvmtiHeapIterationCallback popsané v předchozí kapitole je první položka určena pro uložení ukazatele na callback funkci označenou heap_iteration_callback. S velkou pravděpodobností se jedná o nejčastěji používanou callback funkci, která může být zavolána pro všechny „běžné“ objekty uložené na haldě, tj. jak pro instance tříd, tak i pro pole objektů o libovolné dimenzi, protože tato pole jsou v Javě považována taktéž za běžné objekty. Hlavička callback funkce, jejíž ukazatel může být uložen do položky jvmtiHeapIterationCallbac­k.heap_iteration_callback, musí vypadat následovně:

jint jvmtiHeapIterationCallback (
        jlong  class_tag,
        jlong  size,
        jlong* tag_ptr,
        jint   length,
        void*  user_data);

V tabulce níže je vypsán význam všech pěti parametrů této callback funkce, které jsou správně naplněny při jejím zavolání:

# Jméno Typ Popis
1 class_tag jlong tag přiřazený ke třídě, popř. nula, pokud ke třídě není žádný tag přiřazen
2 size jlong velikost objektu uvedená v bajtech
3 tag_ptr jlong* tag přiřazený k objektu, popř. nula, pokud objektu žádný tak nebyl přiřazen
4 length jint délka pole (pokud je objektem pole), jinak by zde měla být předána hodnota –1
5 user_data void* uživatelská data předávaná této callback funkci

Důležitá je i návratová hodnota této callback funkce, a to z toho důvodu, že s využitím návratové hodnoty je možné do určité míry ovlivňovat průchod haldou. Pokud se vrací hodnota JVMTI_VISIT_OBJECTS, znamená to, že se má pokračovat v dalším procházení haldou, pokud se však vrátí hodnota JVMTI_VISIT_ABORT, je procházení zastaveno. Ve skutečnosti jsou sice možné i další kombinace návratových hodnot callback funkcí, ty se však využijí zejména v případech, kdy se prochází pouze referencemi (to si ukážeme příště):

# Konstanta Hodnota konstanty Význam
1 JVMTI_VISIT_OBJECTS 0×0100 pokračovat v průchodu haldou
2 JVMTI_VISIT_ABORT 0×8000 ukončení průchodu haldou

První verze callback funkce, kterou využijeme v osmnáctém demonstračním příkladu, bude velmi jednoduchá, protože bude pouze vypisovat tag přiřazený ke třídě a taktéž velikost haldy alokovanou pro daný objekt. Tag bude mít vždy hodnotu nula, protože jsme (alespoň prozatím) neprovedli jeho explicitní přenastavení:

/*
 * Callback funkce volana pro kazdy objekt "navstiveny" na halde.
 */
static jint JNICALL callback_for_heap_object(
        jlong  class_tag,
        jlong  size,
        jlong* tag_ptr,
        jint   length,
        void*  user_data)
{
    printf("instance of class with tag #%ld of size %ld\n", (long int)class_tag, (long int)size);
    return JVMTI_VISIT_OBJECTS;
}

3. Osmnáctý demonstrační příklad – průchod všemi objekty uloženými na haldě

Zdrojový kód osmnáctého demonstračního příkladu vychází z příkladů popsaných v předchozích částech tohoto seriálu, proto si zde uvedeme pouze úpravy provedené v některých funkcích (úplné zdrojové kódy najdete v deváté kapitole). V pomocné funkci set_capabilities() nastavíme požadavek na možnost nastavovat tagy pro třídy a objekty (ve skutečnosti tuto možnost využijeme až v dalších příkladech). Musíme mít na paměti, že možnost nastavovat tagy nemusí být implementována všemi virtuálními stroji Javy, ovšem v případě OpenJDK a Oracle JDK je tuto technologii možné využít. Žádné další požadavky nastavovány nebudou, protože na rozdíl od některých předchozích příkladů nepotřebujeme detekovat výjimky ani zjišťovat čísla řádků:

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

Dále v JVM TI agentovi zaregistrujeme tři callback funkce volané samotným virtuálním strojem Javy (tyto callback funkce samozřejmě nemají nic společného s callback funkcí volanou při procházení haldou). První dvě callback funkce již dokonale známe – jsou volány při spuštění a naopak při ukončování činnosti JVM. Poslední callback funkce je nová a je zavolána ve chvíli, kdy přijde systémový požadavek na přečtení dat – typicky se jedná o reakci na ukončení javovského procesu z klávesnice atd. (tímto způsobem si tedy můžeme vynutit výpis haldy kdykoli za běhu JVM):

/*
 * Registrace tri 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_DATA_DUMP_REQUEST */
    callbacks.DataDumpRequest = &callback_on_data_dump_request;
 
    error_code = (*jvmti)->SetEventCallbacks(jvmti, &callbacks, (jint)sizeof(callbacks));
    check_jvmti_error(jvmti, error_code, "Cannot set JVM TI callbacks");
    return error_code;
}

Obsah haldy bude naším demonstračním JVM TI agentem (pro větší jednoduchost implementace) vypsán automaticky ve chvíli, kdy má dojít k ukončení činnosti virtuálního stroje Javy. To znamená, že musíme v callback funkci callback_on_vm_death() nejprve zakázat reakci na příkaz o ukončení JVM z klávesnice a poté provést vlastní výpis haldy. Důvod, proč je reakce na příkaz o ukončení JVM zakázána je prostý – nechceme, aby se halda vypsala několikrát. Taktéž si povšimněte, že celý výpis haldy je umístěn do kritické sekce, což je ostatně vyžadováno i rozhraním JVM TI:

/*
 * Callback funkce zavolana pri ukonceni cinnosti virtualniho stroje.
 */
static void JNICALL callback_on_vm_death(jvmtiEnv *jvmti_env, JNIEnv* env)
{
    MSG("Got VM Death event");
 
    /* veskere operace se budou provadet v kriticke sekci */
    enter_critical_section(jvmti_env);
 
    /* jen pro jistotu zakazeme notifikaci */
    (*jvmti_env)->SetEventNotificationMode(jvmti_env, JVMTI_DISABLE, JVMTI_EVENT_DATA_DUMP_REQUEST, NULL);
 
    /* data request */
    callback_on_data_dump_request(jvmti_env);
 
    /* a vystup z kriticke sekce */
    exit_critical_section(jvmti_env);
}

Přípravné práce jsou dokončeny, nyní nám již zbývá si ukázat, jak vypadá callback funkce callback_on_data_dump_request(). Ta je ve skutečnosti velmi jednoduchá:

/*
 * Callback funkce zavolana pri cteni informaci z haldy.
 */
static void JNICALL callback_on_data_dump_request(jvmtiEnv *jvmti_env)
{
    MSG("Got data dump request");
    register_heap_callback_and_run_dump(jvmti_env);
}

Vidíme, že se z této callback funkce pouze volá další pomocná funkce, která zahájí vlastní proces výpisu haldy – konkrétně zaregistruje příslušnou callback funkci přes datovou strukturu jvmtiHeapCallbacks a nakonec zavolá IterateThroughHeap():

/*
 * Registrace callback funkce pro prochazeni heapem a zacatek prochazeni.
 */
static void register_heap_callback_and_run_dump(jvmtiEnv *jvmti_env)
{
    jvmtiError error;
    jvmtiHeapCallbacks heap_callbacks;
 
    /* naplnit strukturu heap_callbacks jednou callback funkci */
    (void)memset(&heap_callbacks, 0, sizeof(heap_callbacks));
    heap_callbacks.heap_iteration_callback = &callback_for_heap_object;
 
    /* zahajit prochazeni haldou */
    error = (*jvmti_env)->IterateThroughHeap(jvmti_env, 0, NULL,
            &heap_callbacks, NULL);
    check_jvmti_error(jvmti_env, error, "read heap content");
}

Vzhledem k tomu, že prozatím nemáme možnost si vypsat jména tříd pro všechny objekty uložené na haldě, je vcelku jedno, jak bude vypadat demonstrační javovská třída. Použijeme tedy kód využitý i dalšími agenty:

/**
  * Testovaci trida pouzita pro test osmnacteho
  * demonstracniho JVM TI agenta.
  */
public class Test18 {
    /**
      * Spusteni testu.
      */
    public static void main(String[] args) {
        int[]     intArray = new int[1000];
        float[][] floatMatrix = new float[100][100];
        String    hello = "Hello" + ' ' + "world" + '!';
        Object    x = new Test18();
    }
}

Výstup bude poměrně zajímavý, protože se ukazuje, že i JVM spuštěná pro tak jednoduchý příklad bude mít na haldě uloženo přes pět tisíc objektů! Samozřejmě se jedná především o objekty využívané interně virtuálním strojem:

Agent18: Agent_OnLoad
Agent18: JVM TI version is correct
Agent18: Got VM init event
Agent18: Got VM Death event
Agent18: Got data dump request
instance of class with tag #0 of size 16
instance of class with tag #0 of size 24
instance of class with tag #0 of size 48
instance of class with tag #0 of size 24
...
...
... cca 5000(!) řádků
...
...
instance of class with tag #0 of size 32
instance of class with tag #0 of size 640
instance of class with tag #0 of size 32
instance of class with tag #0 of size 96
instance of class with tag #0 of size 32
instance of class with tag #0 of size 464
instance of class with tag #0 of size 40
Agent18: Agent_OnUnload

4. Uživatelská data přenášená do callback funkce při průchodu objekty uloženými na hladě

V předchozích kapitolách jsme si řekli, že při volání JVM TI funkce IterateThroughHeap() je možné v pátém parametru této funkce předat ukazatel na „nějaká“ uživatelská data s tím, že tento ukazatel bude předáván do callback funkce volané pro každý objekt umístěný na heapu. Pokud se do tohoto parametru předá pouze hodnota NULL (což je ostatně zcela legální), bude prostě tato hodnota jednoduše předávána i do callback funkce:

jvmtiError IterateThroughHeap(
            jvmtiEnv* env,
            jint      heap_filter,
            jclass    klass,
            const jvmtiHeapCallbacks* callbacks,
            const void* user_data)

Při pohledu na naši implementaci funkce callback_for_heap_object() je patrné, že se tento ukazatel prozatím nijak nevyužívá:

/*
 * Callback funkce volana pro kazdy objekt "navstiveny" na halde.
 */
static jint JNICALL callback_for_heap_object(
        jlong  class_tag,
        jlong  size,
        jlong* tag_ptr,
        jint   length,
        void*  user_data)
{
    printf("instance of class with tag #%ld of size %ld\n", (long int)class_tag, (long int)size);
    return JVMTI_VISIT_OBJECTS;
}

Ovšem hned v navazující kapitole si ukážeme, jak tento ukazatel využít – bude se jednat o příklad, který taktéž vypíše zjednodušený obsah haldy, ale navíc ještě spočítá celkový počet těchto objektů a počet alokovaných bajtů pro tyto objekty.

5. Devatenáctý demonstrační příklad – výpis souhrnných informací o obsazení haldy (počet objektů a jejich velikost)

Devatenáctý demonstrační JVM TI agent se od předchozího demonstračního agenta liší pouze minimálně, takže si opět uvedeme pouze nové či pozměněné části s tím, že úplné zdrojové texty jsou uvedeny v deváté kapitole. Nejprve je přidána definice velmi jednoduché datové struktury, do níž se budou zaznamenávat počty objektů a jejich celková velikost na haldě. Tato struktura je umístěna na začátku celého programu (jak je ostatně v céčkových programech zvykem):

/* Definice struktura se zakladnimi informacemi o objektech na halde */
typedef struct
{
    int  object_count;
    long total_size;
} t_heap_stat;

Dále došlo ke změně callback funkce volané pro každý objekt umístěný na haldě. Povšimněte si, že nyní předpokládáme, že se v parametru user_data bude předávat ukazatel na výše popsanou datovou strukturu t_heap_stat, takže je nejprve provedeno přetypování původního ukazatele void* na t_heap_stat* a posléze se pozmění hodnota obou prvků uložených v této struktuře – počet objektů a počet alokovaných bajtů:

/*
 * Callback funkce volana pro kazdy objekt "navstiveny" na halde.
 */
static jint JNICALL callback_for_heap_object(jlong class_tag, jlong size, jlong* tag_ptr,
        jint length, void* user_data)
{
    t_heap_stat *heap_stat = (t_heap_stat*)user_data;
 
    heap_stat->object_count++;
    heap_stat->total_size += size;
    printf("instance of class with tag #%ld of size %ld\n", (long int)class_tag, (long int)size);
    return JVMTI_VISIT_OBJECTS;
}

Poslední změna byla provedena ve funkci register_heap_callback_an­d_run_dump(), kde je nejprve na zásobníku vytvořena lokální datová struktura t_heap_stat a posléze je ukazatel na tuto strukturu předán do funkce IterateThroughHeap(). Až tato funkce skončí, tj. až se zavolají všechny callback funkce při průchodu haldou, vypíše se na standardní výstup počet všech objektů na haldě i počet bajtů alokovaných na haldě:

/*
 * Registrace callback funkce pro prochazeni heapem a zacatek prochazeni.
 */
static void register_heap_callback_and_run_dump(jvmtiEnv *jvmti_env)
{
    jvmtiError error;
    jvmtiHeapCallbacks heap_callbacks;
    t_heap_stat heap_stat = {0,0};
 
    /* naplnit strukturu heap_callbacks jednou callback funkci */
    (void)memset(&heap_callbacks, 0, sizeof(heap_callbacks));
    heap_callbacks.heap_iteration_callback = &callback_for_heap_object;
 
    /* zahajit prochazeni haldou */
    error = (*jvmti_env)->IterateThroughHeap(jvmti_env, 0, NULL,
            &heap_callbacks, (const void *)&heap_stat);
    check_jvmti_error(jvmti_env, error, "read heap content");
 
    /* vypis statistiky */
    printf("Object count: %d\n", heap_stat.object_count);
    printf("Total size:   %ld bytes\n", heap_stat.total_size);
}

A jak bude vypadat výstup tohoto demonstračního příkladu? Opět se nejprve vypíšou základní informace o všech objektech umístěných na haldě (jejich tagy by měly být nulové) a navíc se na konci vypíše krátká statistika o využití haldy:

Agent19: Agent_OnLoad
Agent19: JVM TI version is correct
Agent19: Got VM init event
Agent19: Got VM Death event
Agent19: Got data dump request
instance of class with tag #0 of size 16
instance of class with tag #0 of size 24
instance of class with tag #0 of size 48
instance of class with tag #0 of size 24
instance of class with tag #0 of size 40
instance of class with tag #0 of size 24
instance of class with tag #0 of size 88
...
...
... cca 5000(!) řádků
...
...
instance of class with tag #0 of size 32
instance of class with tag #0 of size 640
instance of class with tag #0 of size 32
instance of class with tag #0 of size 96
instance of class with tag #0 of size 32
instance of class with tag #0 of size 464
instance of class with tag #0 of size 40
Object count: 5233
Total size:   607264 bytes
Agent19: Agent_OnUnload

6. Přidání tagů ke třídám

Předchozí dva demonstrační příklady postrádaly jednu velice důležitou vlastnost – nedokázaly vypsat jména tříd pro jednotlivé objekty uložené na haldě. Je tomu tak z toho důvodu, že do callback funkce callback_for_heap_object() se potřebné informace nepředávají. Ovšem můžeme zde využít faktu, že se do této funkce předává tag přiřazený ke třídě. Tagem je zde myšleno celé číslo rozdílné od nuly, protože nula má speciální význam oznamující, že žádný tag nebyl přiřazen. Teoreticky je tedy možné provést následující sekvenci operací:

  1. Získat seznam všech tříd ve virtuálním stroji Javy.
  2. Jména tříd uložit do globálního pole řetězců.
  3. Přiřadit třídám tagy takovým způsobem, aby n-té jméno v poli odpovídalo tagu n (nebo spíše n+1).
  4. V callback funkci callback_for_heap_object() využít předané číslo tagu i globální pole názvů.

První bod lze splnit velmi snadno s využitím JVM TI funkce GetLoadedClasses(), která vrátí jak počet tříd načtených do virtuálního stroje Javy, tak i pole s vyplněnými položkami typu jclass, s nimiž již umíme pracovat:

jvmtiError GetLoadedClasses(
            jvmtiEnv* env,
            jint* class_count_ptr,
            jclass** classes_ptr)

V následujícím úryvku kódu je ukázáno, jak lze tyto informace využít při naplnění globálního pole class_names. Povšimněte si, že v celém kódu jsou použity pouze tři JVM TI funkce GetLoadedClasses(), GetClassSignature() a Deallocate(), protože samozřejmě nesmíme zapomenout na uvolnění paměti alokované funkcí GetClassSignature() (jako malé cvičení si navíc do kódu přidejte dealokaci paměti po GetLoadedClasses()):

/* Globální pole, do nějž se uloží jména všech použitých tříd. */
char **class_names;
 
/* Lokální proměnné použité pouze pro přečtení jmen tříd, posléze nebudou zapotřebí. */
jvmtiError error;
jint       class_count;
jclass    *class_array;
int        i;
 
/* Přečíst jména všech načtených tříd. */
error = (*jvmti_env)->GetLoadedClasses(jvmti_env, &class_count, &class_array);
check_jvmti_error(jvmti_env, error, "get loaded classes");
 
/* Alokovat globální pole, které bude jména obsahovat. */
class_names = (char**)malloc(class_count * sizeof(char*));
 
/* Projít získaným seznamem. */
for (i=0; i < class_count; i++)
{
    char *class_name_ptr;
    char *updated_class_name_ptr;
 
    /* Získat jméno (signaturu) jedné třídy */
    error = (*jvmti_env)->GetClassSignature(jvmti_env, class_array[i], &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 jméno třídy do čitelnější podoby. */
    updated_class_name_ptr = update_class_name(class_name_ptr, ' ');
 
    /* Kopie jména třídy do globálního pole class_names. */
    class_names[i] = (char*)malloc(1 + strlen(updated_class_name_ptr));
    strcpy(class_names[i], updated_class_name_ptr);
 
    /* Dealokace paměti vrácené funkcí GetClassSignature() */
    error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
    check_jvmti_error(jvmti_env, error, "deallocate class name");
}

Funkci update_class_name() jsme si již uvedli v několika předchozích demonstračních JVM TI agentech – jejím úkolem je poněkud „zlidštit“ názvy tříd.

7. Použití tagů při přiřazování objektů k jejich třídám

První a druhý bod našeho teoretického postupu tedy již máme splněn, musíme však ještě implementovat dva další body – přiřadit tag ke každé třídě a následně tento tag vhodně použít v callback funkci. O přiřazení tagu ke třídě či objektu se stará funkce SetTag() s následující hlavičkou:

jvmtiError SetTag(
            jvmtiEnv* env,
            jobject object,
            jlong tag)

Relativně snadno lze tuto funkci vložit do kódu z předchozí kapitoly a následně tento kód zařadit do funkce callback_on_data_dump_request(). Nesmíme zapomenout na jednu maličkost – číslo tagu začíná jedničkou a ne nulou, takže pro tag číslo n bude příslušné jméno třídy uložené v globálním poli class_names pod indexem n-1. Celý zdrojový kód této funkce je sice poměrně dlouhý, ale to jenom z toho důvodu, že je zapotřebí ručně alokovat paměť, pracovat s řetězci s využitím céčkových funkcí a taktéž kontrolovat návratové hodnoty JVM TI funkcí:

/*
 * Callback funkce zavolana pri cteni informaci z haldy.
 */
static void JNICALL callback_on_data_dump_request(jvmtiEnv *jvmti_env)
{
    jvmtiError error;
    jint       class_count;
    jclass    *class_array;
    int        i;
 
    MSG("Got data dump request");
 
    /* Přečíst jména všech načtených tříd. */
    error = (*jvmti_env)->GetLoadedClasses(jvmti_env, &class_count, &class_array);
    check_jvmti_error(jvmti_env, error, "get loaded classes");
 
    /* Alokovat globální pole, které bude jména obsahovat. */
    class_names = (char**)malloc(class_count * sizeof(char*));
 
    /* Projít získaným seznamem. */
    for (i=0; i < class_count; i++)
    {
        char *class_name_ptr;
        char *updated_class_name_ptr;
 
        /* Získat jméno (signaturu) jedné třídy */
        error = (*jvmti_env)->GetClassSignature(jvmti_env, class_array[i], &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 jméno třídy do čitelnější podoby. */
        updated_class_name_ptr = update_class_name(class_name_ptr, ' ');
 
        /* Kopie jména třídy do globálního pole class_names. */
        class_names[i] = (char*)malloc(1 + strlen(updated_class_name_ptr));
        strcpy(class_names[i], updated_class_name_ptr);
 
        printf("class %s has tag %d\n", class_names[i], i + 1);
 
        /* Dealokace paměti vrácené funkcí GetClassSignature() */
        error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr);
        check_jvmti_error(jvmti_env, error, "deallocate class name");
 
        /* Nastavení tagu pro třídu. */
        error = (*jvmti_env)->SetTag(jvmti_env, class_array[i], i + 1);
        check_jvmti_error(jvmti_env, error, "deallocate class name");
    }
    register_heap_callback_and_run_dump(jvmti_env);
}

8. Dvacátý demonstrační příklad – výpis všech objektů na haldě včetně jména příslušné třídy

Poslední variantu funkce callback_on_data_dump_request() uvedenou v předchozí kapitole použijeme ve dvacátém demonstračním JVM TI agentovi. Tato funkce připraví globální seznam jmen tříd a přiřadí každé třídě nějaký tag, tj. celé číslo odlišné od nuly. Následně je zavolána funkce register_heap_callback_an­d_run_dump(), která zaregistruje callback funkci přes datovou strukturu jvmtiHeapCallbacks, zavolá funkci IterateThroughHeap() a následně na standardní výstup vypíše počet objektů uložených na haldě i celkovou obsazenost haldy v bajtech:

/*
 * Registrace callback funkce pro prochazeni heapem a zacatek prochazeni.
 */
static void register_heap_callback_and_run_dump(jvmtiEnv *jvmti_env)
{
    jvmtiError error;
    jvmtiHeapCallbacks heap_callbacks;
    t_heap_stat heap_stat = {0,0};
 
    /* naplnit strukturu heap_callbacks jednou callback funkci */
    (void)memset(&heap_callbacks, 0, sizeof(heap_callbacks));
    heap_callbacks.heap_iteration_callback = &callback_for_heap_object;
 
    /* zahajit prochazeni haldou */
    error = (*jvmti_env)->IterateThroughHeap(jvmti_env, 0, NULL,
            &heap_callbacks, (const void *)&heap_stat);
    check_jvmti_error(jvmti_env, error, "read heap content");
 
    /* vypis statistiky */
    printf("Object count: %d\n", heap_stat.object_count);
    printf("Total size:   %ld bytes\n", heap_stat.total_size);
}

Jak zajisté očekáváte, došlo i ke změně samotné callback funkce callback_for_heap_object() volané pro každý objekt na haldě. Vylepšená varianta této funkce použije globální seznam jmen tříd class_names společně s hodnotou parametru class_tag k získání jména třídy, z níž byl daný objekt získán. V produkčním kódu by zde bylo nutné přidat kontrolu, zda se náhodou nepřekračuje kapacita pole class_names, zde se to pro jednoduchost neprovádí:

/*
 * Callback funkce volana pro kazdy objekt "navstiveny" na halde.
 */
static jint JNICALL callback_for_heap_object(jlong class_tag, jlong size, jlong* tag_ptr,
        jint length, void* user_data)
{
    int index = class_tag - 1;
    t_heap_stat *heap_stat = (t_heap_stat*)user_data;
 
    heap_stat->object_count++;
    heap_stat->total_size += size;
    printf("instance of class %s with tag #%ld of size %ld\n",
            class_names[index], (long int)class_tag, (long int)size);
    return JVMTI_VISIT_OBJECTS;
}

Z výpisu práce tohoto demonstračního agenta je patrné, že jsme se konečně dostali k požadovaným jménům tříd. Ty jsou vypsány hned na začátku:

Agent20: Agent_OnLoad
Agent20: JVM TI version is correct
Agent20: Got VM init event
Agent20: Got VM Death event
Agent20: Got data dump request
class java.io.BufferedOutputStream  has tag 1
class java.util.Enumeration  has tag 3
class sun.misc.Version  has tag 4
class sun.misc.ExtensionDependency  has tag 5
class java.nio.HeapByteBuffer  has tag 6
class java.util.Arrays  has tag 7
class sun.misc.JavaIOAccess  has tag 8
class java.util.IdentityHashMap  has tag 9
class java.net.URLStreamHandlerFactory  has tag 10
class java.util.AbstractCollection  has tag 11
class java.util.AbstractMap  has tag 12
class java.util.concurrent.locks.AbstractOwnableSynchronizer  has tag 13
class java.lang.ClassCastException  has tag 14
class java.lang.Class  has tag 169
...
...
...

Mnoho tříd obsahuje ve svém jménu znak „$“. V tomto případě se v některých případech jedná o vnitřní třídy:

class java.lang.Thread$UncaughtExceptionHandler  has tag 15
class java.util.HashMap$KeySet  has tag 49
class java.util.IdentityHashMap$KeyIterator  has tag 73
class java.util.concurrent.locks.ReentrantLock$Sync  has tag 179

A v dalších případech pak o třídy anonymní:

class java.lang.Class$3  has tag 2
class java.io.File$1  has tag 68
class java.lang.Class$1  has tag 154
class java.io.File$2  has tag 178
class java.nio.charset.CoderResult$2  has tag 180

U některých jmen naleznete na začátku jeden či více znaků „[“, jejichž význam však již známe – označují pole, popř. vícerozměrné pole o tolika dimenzích, kolik znaků je zde umístěno. Typy prvků pole jsou zapsány ihned za těmito závorkami:

class [Ljava.lang.Comparable  has tag 88
class [Ljava.io.ObjectStreamField  has tag 93
class [Ljava.util.Map$Entry  has tag 221
class [Ljava.lang.String  has tag 230

Poslední speciální skupinou tříd jsou pole obsahující jako své položky primitivní datové typy. Tato pole opět začínají jedním či více znaky „[“, ovšem namísto jmen tříd se používají jednopísmenné zkratky odpovídající základním datovým typům Javy:

class [Z has tag 372
class [B has tag 373
class [C has tag 374
class [I has tag 375
class [S has tag 376
class [J has tag 377
class [F has tag 378
class [[F has tag 379
class [D has tag 380

Převodní tabulku pro tyto kódy jsme si již v tomto seriálu několikrát uvedli, ale možná nebude na škodu si ji zopakovat:

# Znak Datový typ Význam
1 B byte osmibitové celé číslo se znaménkem
2 C char znak reprezentovaný v Unikódu
3 D double číslo s plovoucí řádovou čárkou s dvojitou přesností
4 F float číslo s plovoucí řádovou čárkou s jednoduchou přesností
5 I int 32bitové celé číslo se znaménkem
6 J long 64bitové celé číslo se znaménkem
7 S short 16bitové celé číslo se znaménkem
8 Z boolean pravdivostní hodnota true/false
9 V void void – pouze u návratového typu (nebude použito ve výpisu haldy)

9. Zdrojové kódy všech tří demonstračních příkladů

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

10. 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?
Měšec.cz: Kdy vám stát dá na stěhování 50 000 Kč?

Kdy vám stát dá na stěhování 50 000 Kč?

120na80.cz: Rovnátka, která nejsou vidět

Rovnátka, která nejsou vidět

Podnikatel.cz: Přehledná titulka, průvodci, responzivita

Přehledná titulka, průvodci, responzivita

Vitalia.cz: Jak vybrat ořechy do cukroví a kde mají levné

Jak vybrat ořechy do cukroví a kde mají levné

Root.cz: Vypadl Google a rozbilo se toho hodně

Vypadl Google a rozbilo se toho hodně

Podnikatel.cz: Chaos u EET pokračuje. Jsou tu další návrhy

Chaos u EET pokračuje. Jsou tu další návrhy

Vitalia.cz: Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Láska na vozíku: Přitažliví jsme pro tzv. pečovatelky

Vitalia.cz: Chtějí si léčit kvasinky. Lék je jen v Německu

Chtějí si léčit kvasinky. Lék je jen v Německu

Podnikatel.cz: EET: Totálně nezvládli metodologii projektu

EET: Totálně nezvládli metodologii projektu

120na80.cz: Pánové, pečujte o svoje přirození a prostatu

Pánové, pečujte o svoje přirození a prostatu

Vitalia.cz: Jsou čajové sáčky toxické?

Jsou čajové sáčky toxické?

Měšec.cz: Air Bank zruší TOP3 garanci a zdražuje kurzy

Air Bank zruší TOP3 garanci a zdražuje kurzy

Podnikatel.cz: Na poslední chvíli šokuje výjimkami v EET

Na poslední chvíli šokuje výjimkami v EET

Vitalia.cz: Znáte „černý detox“? Ani to nezkoušejte

Znáte „černý detox“? Ani to nezkoušejte

Vitalia.cz: Paštiky plné masa ho zatím neuživí

Paštiky plné masa ho zatím neuživí

Podnikatel.cz: Babiše přesvědčila 89letá podnikatelka?!

Babiše přesvědčila 89letá podnikatelka?!

Lupa.cz: Teletext je „internetem hipsterů“

Teletext je „internetem hipsterů“

DigiZone.cz: ČT má dalšího zástupce v EBU

ČT má dalšího zástupce v EBU

Root.cz: Certifikáty zadarmo jsou horší než za peníze?

Certifikáty zadarmo jsou horší než za peníze?

Podnikatel.cz: Podnikatelům dorazí varování od BSA

Podnikatelům dorazí varování od BSA