Pohled pod kapotu JVM - průchod hodnotami atributů objektů uložených na haldě (heapu)

Pavel Tišnovský 8. 1. 2013

Dnes si společně řekneme, jak je možné s využitím JVM TI agenta vypsat hodnoty všech statických a nestatických atributů uložených na haldě. Zaměříme se zejména na průchod atributy základních (primitivních) datových typů, protože ty se zpracovávají odlišně od objektových (referenčních) typů.

Obsah

1. Pohled pod kapotu JVM – průchod hodnotami atributů objektů uložených na haldě (heapu)

2. Callback funkce volaná při průchodu hodnotami primitivních datových typů uložených na haldě

3. Rozlišení mezi atributem třídy (statickým atributem) a atributem objektu (nestatickým atributem)

4. Zjištění typu hodnoty uložené na haldě

5. Zjištění třídy, k níž atribut uložený na haldě přísluší

6. Demonstrační agent číslo 21: výpis atributů pro všechny instance tříd „Foo“ a „Bar“

7. Výpočet obsazení haldy hodnotami primitivních datových typů

8. Tisk všech hodnot nalezených agentem

9. Demonstrační agent číslo 22: podrobnější výpis atributů pro instance tříd „Foo“ a „Bar“

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

11. Odkazy na Internetu

1. Pohled pod kapotu JVM – průchod hodnotami atributů objektů uložených na haldě (heapu)

V dnešní (již šedesáté :-) části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje Javy si popíšeme a následně na dvojici demonstračních agentů i prakticky ukážeme, jakým způsobem je možné přes rozhraní JVM TI procházet všemi (neobjektovými) atributy tříd i atributy objektů uložených na haldě (heapu). Připomeňme si, že atributy tříd (=statické atributy) i atributy objektů (=nestatické atributy) mohou být, podobně jako lokální proměnné či parametry metod, buď objektového typu (instancí nějaké třídy, která opět rekurzivně může obsahovat další objekty nebo primitivní hodnoty) nebo mohou být představovány takzvaným primitivním datovým typem, což je v případě programovacího jazyka Java jeden z následujících osmi typů:

# Primitivní datový typ Skupina Rozsah hodnot
1 boolean pravdivostní typ true,false
2 byte celočíselný typ –128..127
3 short celočíselný typ –32768..32767
4 int celočíselný typ –231..231-1
5 long celočíselný typ –263..263-1
6 char znakový typ 0..232-1
7 float číselný typ s plovoucí řádovou čárkou ±3.40282347×1038
8 double číselný typ s plovoucí řádovou čárkou ±1.79769313486231570×10308

Atributy všech živých objektů jsou samozřejmě při běhu virtuálního stroje Javy uloženy na haldě, podobně jako vlastní instance tříd (objekty). Rozhraní JVM TI obsahuje potřebnou funkcionalitu i pro procházení atributy; v podstatě se jedná o stejný princip, s nímž jsme se již seznámili minule, ovšem s tím rozdílem, že se při procházení atributy uloženými na haldě použije jiný typ callback funkce, jejíž parametry jsou odlišné od callback funkce používané pro každý běžný objekt. Inicializaci průchodu haldou (heapem) budeme, podobně jako v předchozí části tohoto seriálu, iniciovat pomocí JVM TI funkce nazvané IterateThroughHeap() a na ni navázaných callback funkcí. 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 všech parametrů této funkce jsme si již popsali předminule a minule, v dnešních dvou demonstračních příkladech popsaných v následujících kapitolách však dojde ke změně obsahu struktury typu jvmtiHeapCallbacks, která se předává jako čtvrtý parametr JVM TI funkci IterateThroughHeap(). Struktura jvmtiHeapCallbacks je navržena takovým způsobem, aby mohla pojmout celkem šestnáct ukazatelů na různé typy callback funkcí. Ve skutečnosti je však v současné verzi rozhraní JVM TI použito pouze pět typů callback funkcí, takže by dalších jedenáct ukazatelů mělo mít hodnotu NULL (není to sice striktně vyžadováno, ale je to bezpečnější v případě, kdyby se agent použil na novější verzi JVM):

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;

2. Callback funkce volaná při průchodu hodnotami primitivních datových typů uložených na haldě

Význam pěti v současnosti přesně definovaných ukazatelů na callback funkce uložený v datové struktuře jvmtiHeapCallbacks 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áno 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

Callback funkce jvmtiHeapIterationCallback() byla již poměrně dopodrobna popsána v předchozí části tohoto seriálu, kde jsme ji použili ve třech demonstračních JVM TI agentech. Tato callback funkce může být zavolána pro každý objekt nalezený na haldě, přičemž se jí pokaždé předají nejdůležitější informace o nalezeném objektu – jeho třída, velikost objektu v bajtech, tag přiřazený k objektu (nebo NULL, pokud tag přiřazen není), délka pole v případě, že je objektem pole a konečně ukazatel na uživatelská data, který byl předán funkci IterateThroughHeap() při inicializaci průchodu haldou:

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

V případě, že potřebujeme procházet atributy objektů (přesněji řečeno atributy primitivního typu), je nutné namísto callback funkce jvmtiHeapIterationCallback() zaregistrovat a následně použít funkci jvmtiPrimitiveFieldCallback(), jejíž hlavička je v mnoha ohledech od jvmtiHeapIterationCallback() odlišná, což je ostatně pochopitelné, protože o atributech (a především o primitivních hodnotách) potřebujeme znát odlišné informace, než o instancích tříd:

jint jvmtiPrimitiveFieldCallback (
        jvmtiHeapReferenceKind        kind,
        const jvmtiHeapReferenceInfo* info,
        jlong                         object_class_tag,
        jlong*                        object_tag_ptr,
        jvalue                        value,
        jvmtiPrimitiveType            value_type,
        void*                         user_data);

Tento typ callback funkce je možné v agentovi zaregistrovat velmi snadno – následující úryvek kódu je prakticky shodný s kódem použitým v demonstračních příkladech minule, liší se pouze řádek, v němž je naplňována datová struktura jvmtiHeapCallbacks heap_callbacks:

/*
 * 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};
 
    /* naplnit strukturu heap_callbacks jednou callback funkci */
    (void)memset(&heap_callbacks, 0, sizeof(heap_callbacks));
    heap_callbacks.primitive_field_callback = &callback_for_primitive_values;
 
    /* 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("Primitive values count: %d (fields only)\n", heap_stat.primitive_count);
}

Vlastní volaná callback funkce pak může ve zcela minimalistické variantě vypadat následovně:

/*
 * Callback funkce volana pro kazdou hodnotu primitivniho datoveho typu
 * ulozenou na halde.
 */
static jint JNICALL callback_for_primitive_values(
        jvmtiHeapReferenceKind        kind,
        const jvmtiHeapReferenceInfo* info,
        jlong                         object_class_tag,
        jlong*                        object_tag_ptr,
        jvalue                        value,
        jvmtiPrimitiveType            value_type,
        void*                         user_data)
{
    /* pokracovat dal v prochazeni haldy */
    return JVMTI_VISIT_OBJECTS;
}

Tato callback funkce bude zavolána pro každý atribut primitivního datového typu, který bude na haldě nalezen. O to, aby se prošla skutečně celá halda, se stará návratová hodnota JVMTI_VISIT_OBJECTS , přesněji řečeno jakákoli hodnota, která neobsahuje nastavený bit JVMTI_VISIT_ABORT. Ještě však neznáme, jaké hodnoty se vlastně této callback funkci pro každý atribut předávají. Popis jednotlivých parametrů této callback funkce je uveden v následující tabulce:

# Typ parametru Jméno parametru Význam
1 jvmtiHeapReferenceKind kind rozlišení, zda se jedná o atribut třídy nebo atribut objektu
2 jvmtiHeapReferenceInfo* info ukazatel na unii struktur, kde je uložen index (pořadí) atributu (viz další text)
3 jlong object_class_tag tag přiřazený ke třídě, které je objekt instancí
4 jlong* object_tag_ptr ukazatel na tag přiřazený k objektu
5 jvalue value hodnota atributu (jde o unii, viz další text)
6 jvmtiPrimitiveType value_type typ hodnoty (byte, int, float…)
7 void* user_data ukazatel na uživatelská data (má stejný význam, jaký již známe z minula)

3. Rozlišení mezi atributem třídy (statickým atributem) a atributem objektu (nestatickým atributem)

Podívejme se nyní na význam prvního parametru callback funkce callback_for_primitive_values(). Tento parametr je typu jvmtiHeapReferenceKind a při pohledu na obsah hlavičkového souboru jvmti.h můžeme jednoduše zjistit, že se jedná o výčtový typ, tj. callback funkci je ve skutečnosti předáno celé číslo typu int:

typedef enum {
        JVMTI_HEAP_REFERENCE_CLASS = 1,
        JVMTI_HEAP_REFERENCE_FIELD = 2,
        JVMTI_HEAP_REFERENCE_ARRAY_ELEMENT = 3,
        JVMTI_HEAP_REFERENCE_CLASS_LOADER = 4,
        JVMTI_HEAP_REFERENCE_SIGNERS = 5,
        JVMTI_HEAP_REFERENCE_PROTECTION_DOMAIN = 6,
        JVMTI_HEAP_REFERENCE_INTERFACE = 7,
        JVMTI_HEAP_REFERENCE_STATIC_FIELD = 8,
        JVMTI_HEAP_REFERENCE_CONSTANT_POOL = 9,
        JVMTI_HEAP_REFERENCE_SUPERCLASS = 10,
        JVMTI_HEAP_REFERENCE_JNI_GLOBAL = 21,
        JVMTI_HEAP_REFERENCE_SYSTEM_CLASS = 22,
        JVMTI_HEAP_REFERENCE_MONITOR = 23,
        JVMTI_HEAP_REFERENCE_STACK_LOCAL = 24,
        JVMTI_HEAP_REFERENCE_JNI_LOCAL = 25,
        JVMTI_HEAP_REFERENCE_THREAD = 26,
        JVMTI_HEAP_REFERENCE_OTHER = 27
} jvmtiHeapReferenceKind;

Ve skutečnosti se však nemusíme rozhodovat mezi sedmnácti hodnotami, které jsou v tomto výčtovém typu definovány, protože je zaručeno, že v prvním parametru callback funkce callback_for_primitive_values() může být uložena pouze hodnota JVMTI_HEAP_REFERENCE_STATIC_FIELD pro atributy tříd, popř. hodnota JVMTI_HEAP_REFERENCE_FIELD pro atributy objektů.

4. Zjištění typu hodnoty uložené na haldě

Dalším parametrem callback funkce callback_for_primitive_values(), kterým se budeme zabývat, je předposlední parametr nazvaný value_type, jehož datovým typem je jvmtiPrimitiveType. Patrně jste již správně uhodli, že se v tomto parametru předává typ atributu. Jak jsme si již řekli v první kapitole, rozeznáváme v programovacím jazyku Java celkem osm primitivních datových typů:

typedef enum {
        JVMTI_PRIMITIVE_TYPE_BOOLEAN = 90,
        JVMTI_PRIMITIVE_TYPE_BYTE = 66,
        JVMTI_PRIMITIVE_TYPE_CHAR = 67,
        JVMTI_PRIMITIVE_TYPE_SHORT = 83,
        JVMTI_PRIMITIVE_TYPE_INT = 73,
        JVMTI_PRIMITIVE_TYPE_LONG = 74,
        JVMTI_PRIMITIVE_TYPE_FLOAT = 70,
        JVMTI_PRIMITIVE_TYPE_DOUBLE = 68
} jvmtiPrimitiveType;

V prvním demonstračním příkladu popsaném dále budeme mj. vypisovat i typy atributů. O převod mezi číselně vyjádřeným datovým typem a popisným řetězcem se postará funkce get_primitive_type_str(), v níž bylo jednodušší použít rozvětvení typu switch z toho důvodu, že číselné kódy datových typů (kupodivu) netvoří celočíselnou posloupnost:

/**
 * Prevod ciselneho identifikatoru datoveho typu na retezec.
 */
char *get_primitive_type_str(jvmtiPrimitiveType value_type)
{
    switch (value_type)
    {
        case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
            return "boolean";
            break;
        case JVMTI_PRIMITIVE_TYPE_BYTE:
            return "byte";
            break;
        case JVMTI_PRIMITIVE_TYPE_CHAR:
            return "char";
            break;
        case JVMTI_PRIMITIVE_TYPE_SHORT:
            return "short";
            break;
        case JVMTI_PRIMITIVE_TYPE_INT:
            return "int";
            break;
        case JVMTI_PRIMITIVE_TYPE_LONG:
            return "long";
            break;
        case JVMTI_PRIMITIVE_TYPE_FLOAT:
            return "float";
            break;
        case JVMTI_PRIMITIVE_TYPE_DOUBLE:
            return "double";
            break;
        default: /* nemelo by nikdy nastat */
            return "unknown!";
            break;
    }
}

5. Zjištění třídy, k níž atribut uložený na haldě přísluší

Dalším důležitým parametrem callback funkce callback_for_primitive_values() je třetí parametr pojmenovaný object_class_tag. S tímto parametrem jsme se vlastně již setkali, i když v poněkud odlišné podobě, i v předchozí části tohoto seriálu – v tomto parametru je totiž předán tag přiřazený ke třídě, k níž přísluší právě nalezený statický/třídní atribut, popř. ke třídě, jejíž instance je právě procházena. My již víme, jakým způsobem je možné tag využít; dokážeme totiž již při načítání tříd, popř. po zavolání funkce GetLoadedClasses() každé třídě nastavit nenulový tag a současně si uložit jméno třídy do globálního pole řetězců. Následně je možné s využitím předaného tagu převést tuto číselnou hodnotu na jméno třídy. Globální pole class_names lze naplnit například následujícím kódem:

/* Globální pole, do něhož 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");
}

Nyní již můžeme callback funkci callback_for_primitive_values() upravit takovým způsobem, že pro každý nalezený (statický i nestatický) atribut vypíše jak jeho typ, tak i jméno příslušné třídy:

/*
 * Callback funkce volana pro kazdou hodnotu primitivniho datoveho typu
 * ulozenou na halde.
 */
static jint JNICALL callback_for_primitive_values(
        jvmtiHeapReferenceKind        kind,
        const jvmtiHeapReferenceInfo* info,
        jlong                         object_class_tag,
        jlong*                        object_tag_ptr,
        jvalue                        value,
        jvmtiPrimitiveType            value_type,
        void*                         user_data)
{
    int index;
    char *primitive_type_str;
    char *class_name;
 
    /* na zaklade predaneho typu ziskat jmeno typu jako retezec */
    primitive_type_str = get_primitive_type_str(value_type);
 
    /* jmeno tridy lze ziskat na zaklade tagu z globalniho pole */
    index = object_class_tag - 1;
    class_name = class_names[index];
 
    /* vypiseme informace */
    printf("Found field of type %-7s in instance of class %s\n",
        primitive_type_str, class_name);
 
    /* spocitani statistickych informaci o obsazeni haldy */
    t_heap_stat *heap_stat = (t_heap_stat*)user_data;
    heap_stat->primitive_count++;
 
    /* pokracovat dal v prochazeni haldy */
    return JVMTI_VISIT_OBJECTS;
}

6. Demonstrační agent číslo 21: výpis atributů pro všechny instance tříd „Foo“ a „Bar“

Konečně se dostáváme k dnešnímu prvnímu demonstračnímu příkladu – JVM TI agentu. V tomto příkladu se vypíšou typy všech nestatických atributů instancí tříd Foo a Bar. Instance těchto dvou tříd jsou vytvořeny v metodě Test21.main():

/**
  * Testovaci trida pouzita pro test dvacateho
  * prvniho demonstracniho JVM TI agenta.
  */
public class Test21 {
 
    /**
      * Spusteni testu.
      */
    public static void main(String[] args) {
        Foo foo1 = new Foo(42, 3.1415f, false);
        Foo foo2 = new Foo(6502, 2.7172f, true);
        Bar bar1 = new Bar();
        Bar bar2 = new Bar();
    }
}
 
class Foo {
 
    boolean booleanValue;
    int     intValue;
    float   floatValue;
 
    public Foo(int intValue, float floatValue, boolean booleanValue) {
        this.intValue = intValue;
        this.floatValue = floatValue;
        this.booleanValue = booleanValue;
    }
 
}
 
class Bar {
 
    byte  b;
    short s;
    int  i;
    long l;
 
    public Bar() {
        this.b = (byte)1;
        this.s = (short)2;
        this.i = 3;
        this.l = 4L;
    }
 
}

Vzhledem k tomu, že potřebujeme vypsat jen nestatické atributy, budeme muset do callback funkce callback_for_primitive_values() přidat kontrolu na to, zda je v parametru kind předána hodnota JVMTI_HEAP_REFERENCE_FIELD. Druhou filtraci provedeme na základě jména třídy, protože potřebujeme vypsat pouze atributy instancí tříd Foo a Bar. Rychlejší by sice bylo si zapamatovat tagy těchto tříd a potom provádět pouze porovnání dvou celých čísel a nikoli porovnávání řetězců, celý program by to však zbytečně zkomplikovalo. Výsledná podoba callback funkce je vypsána pod tímto odstavcem:

/*
 * Callback funkce volana pro kazdou hodnotu primitivniho datoveho typu
 * ulozenou na halde.
 */
static jint JNICALL callback_for_primitive_values(
        jvmtiHeapReferenceKind        kind,
        const jvmtiHeapReferenceInfo* info,
        jlong                         object_class_tag,
        jlong*                        object_tag_ptr,
        jvalue                        value,
        jvmtiPrimitiveType            value_type,
        void*                         user_data)
{
    int index;
    char *primitive_type_str;
    char *class_name;
 
    /* zajimaji nas pouze nestaticke atributy objektu */
    if (kind != JVMTI_HEAP_REFERENCE_FIELD) return JVMTI_VISIT_OBJECTS;
 
    /* na zaklade predaneho typu ziskat jmeno typu jako retezec */
    primitive_type_str = get_primitive_type_str(value_type);
 
    /* jmeno tridy lze ziskat na zaklade tagu z globalniho pole */
    index = object_class_tag - 1;
    class_name = class_names[index];
 
    /* vypiseme informace pouze pro dve testovaci tridy "Foo" a "Bar" */
    if (strcmp("Foo ", class_name)==0 || strcmp("Bar ", class_name)==0)
    {
        printf("Found field of type %-7s in instance of class %s\n",
                primitive_type_str, class_name);
    }
 
    /* spocitani statistickych informaci o obsazeni haldy */
    t_heap_stat *heap_stat = (t_heap_stat*)user_data;
    heap_stat->primitive_count++;
 
    /* pokracovat dal v prochazeni haldy */
    return JVMTI_VISIT_OBJECTS;
}

Kompletní zdrojový kód 21.demonstračního agenta i všechny potřebné soubory použité pro jeho překlad a spuštění můžete nalézt v desáté kapitole. Zde si jen ukážeme zprávy vypisované agentem na standardní výstup:

Agent21: Agent_OnLoad
Agent21: JVM TI version is correct
Agent21: Got VM init event
Agent21: Got VM Death event
Agent21: Got data dump request
class java.io.BufferedOutputStream  has tag 1
class java.lang.Class$3  has tag 2
class java.util.Enumeration  has tag 3
class sun.misc.Version  has tag 4
...
...
...
class [Z has tag 373
class [B has tag 374
class [C has tag 375
class [I has tag 376
class [S has tag 377
class [J has tag 378
class [F has tag 379
class [D has tag 380
Found field of type float   in instance of class Foo
Found field of type int     in instance of class Foo
Found field of type boolean in instance of class Foo
Found field of type float   in instance of class Foo
Found field of type int     in instance of class Foo
Found field of type boolean in instance of class Foo
Found field of type long    in instance of class Bar
Found field of type int     in instance of class Bar
Found field of type short   in instance of class Bar
Found field of type byte    in instance of class Bar
Found field of type long    in instance of class Bar
Found field of type int     in instance of class Bar
Found field of type short   in instance of class Bar
Found field of type byte    in instance of class Bar
Primitive values count: 5522 (fields only)
Agent21: Agent_OnUnload

Z výpisu je patrné, že se nejdříve každé načtené třídě přiřadí celočíselný tag rozdílný od nuly a následně se při průchodu haldou vypíšou datové typy atributů instancí tříd Foo a Bar. Nakonec je vypsán celkový počet atributů (základního datového typu), které byly na haldě nalezeny.

7. Výpočet obsazení haldy hodnotami primitivních datových typů

Další úpravy demonstračního agenta již budou poměrně jednoduché. Do statistiky obsazení haldy nejprve přidáme i informaci o tom, kolik místa je zde vyhrazeno pro atributy základních datových typů. Dopředu je nutné říci, že se bude jednat pouze o odhad (a to ještě o dolní hranici tohoto odhadu), protože každá implementace virtuálního stroje Javy si může sama rozhodnout, zda se bude při ukládání používat nějaká forma zarovnání dat, například na celočíselné násobky 32bitových či dnes spíše 64bitových adres. I tak se však jedná o zajímavé informace, zvláště když si uvědomíme, že primitivní datové typy a pole těchto typů jsou vlastně jedinými „skutečnými“ daty uloženými na haldě, protože objekty jsou zde uloženy ve formě referencí, které v důsledku zase ukazují pouze na hodnoty primitivních datových typů. Pro výpočet minimálního místa na haldě obsazené určitou hodnotou je použita pomocná funkce get_primitive_type_size(). Její chování je jednoduché, snad až na výpočet velikosti u typu boolean, kde teoreticky postačuje pro uložení hodnoty true/false pouze jeden bit, nicméně JVM většinou pracuje s celým bajtem (a v bajtkódu je navíc boolean zpracováván jako int, podobně jako je tomu u typů byte a short). Z tohoto důvodu se budeme držet rozhraní JNI, kde je jasně řečeno, že se hodnoty typu boolean předávají v jako osmibitové hodnoty:

/**
 * Ziskani velikosti (bajty) hodnoty daneho primitivniho datoveho typu.
 */
int get_primitive_type_size(jvmtiPrimitiveType value_type)
{
    switch (value_type)
    {
        case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
        case JVMTI_PRIMITIVE_TYPE_BYTE:
            return 1;
            break;
        case JVMTI_PRIMITIVE_TYPE_CHAR:
        case JVMTI_PRIMITIVE_TYPE_SHORT:
            return 2;
            break;
        case JVMTI_PRIMITIVE_TYPE_INT:
        case JVMTI_PRIMITIVE_TYPE_FLOAT:
            return 4;
            break;
        case JVMTI_PRIMITIVE_TYPE_LONG:
        case JVMTI_PRIMITIVE_TYPE_DOUBLE:
            return 8;
            break;
        default: /* nemelo by nastat */
            return 0;
            break;
    }
}

8. Tisk všech hodnot nalezených agentem

Dalším úkolem agenta bude tisk hodnot primitivních datových typů. Jak jsme se již dozvěděli ve druhé kapitole, je hodnota jakéhokoli primitivního datového typu předána v pátém parametru funkce callback_for_primitive_values(). Tento parametr je typu jvalue a pohledem do hlavičkového souboru jni.h (nikoli jvmti.h!) snadno zjistíme, že se jedná o unii (v céčku union), v níž může být uložena jak hodnota jakéhokoli základního datového typu, tak i reference na objekt. Nás však budou v tuto chvíli zajímat pouze hodnoty základních datových typů a nikoli reference, takže z unie jvalue budeme používat pouze prvních osm (v paměti se překrývajících) prvků:

typedef union jvalue {
        jboolean z;
        jbyte    b;
        jchar    c;
        jshort   s;
        jint     i;
        jlong    j;
        jfloat   f;
        jdouble  d;
        jobject  l;
} jvalue;

Funkce, která provede tisk jakékoli hodnoty základního datového typu na standardní výstup, může vypadat například následovně (úprava pro GCC v případě céčkového datového typu long long):

/**
 * Tisk hodnoty primitivniho datoveho typu na standardni vystup.
 */
void print_value(jvmtiPrimitiveType value_type, jvalue value)
{
    switch (value_type)
    {
        case JVMTI_PRIMITIVE_TYPE_BOOLEAN:
            puts(value.z ? "true" : "false");
            break;
        case JVMTI_PRIMITIVE_TYPE_CHAR:
            printf("%d\n", (int)value.c);
            break;
        case JVMTI_PRIMITIVE_TYPE_BYTE:
            printf("%d\n", (int)value.b);
            break;
        case JVMTI_PRIMITIVE_TYPE_SHORT:
            printf("%d\n", (int)value.s);
            break;
        case JVMTI_PRIMITIVE_TYPE_INT:
            printf("%d\n", (int)value.i);
            break;
        case JVMTI_PRIMITIVE_TYPE_LONG:
            printf("%Ld\n", (long long)value.j);
            break;
        case JVMTI_PRIMITIVE_TYPE_FLOAT:
            printf("%f\n", value.f);
            break;
        case JVMTI_PRIMITIVE_TYPE_DOUBLE:
            printf("%lf\n", value.d);
            break;
        default:
            puts("unknown!");
            break;
    }
}

9. Demonstrační agent číslo 22: podrobnější výpis atributů pro instance tříd „Foo“ a „Bar“

Pomocné funkce popsané v předchozích dvou kapitolách nyní můžeme využít v dalším (22.) demonstračním JVM TI agentovi, který pro každý atribut nalezený na haldě vypíše příslušnou třídu, typ atributu (boolean, float, int…) a taktéž jeho hodnotu. Přitom se provádí filtrace na atributy tříd Foo a Bar; tento filtr lze samozřejmě velmi snadno odstranit, pokud budete potřebovat vypsat všech přibližně 5500 atributů :-) Kromě obou nových pomocných funkcí get_primitive_type_size() a get_primitive_type_str() došlo ke změně v callback funkci callback_for_primitive_values():

widgety

/*
 * Callback funkce volana pro kazdou hodnotu primitivniho datoveho typu
 * ulozenou na halde.
 */
static jint JNICALL callback_for_primitive_values(
        jvmtiHeapReferenceKind        kind,
        const jvmtiHeapReferenceInfo* info,
        jlong                         object_class_tag,
        jlong*                        object_tag_ptr,
        jvalue                        value,
        jvmtiPrimitiveType            value_type,
        void* user_data)
{
    int index;
    int size;
    char *primitive_type_str;
    char *class_name;
 
    /* zajimaji nas pouze nestaticke atributy objektu */
    if (kind != JVMTI_HEAP_REFERENCE_FIELD) return JVMTI_VISIT_OBJECTS;
 
    /* na zaklade predaneho typu ziskat jmeno typu jako retezec */
    primitive_type_str = get_primitive_type_str(value_type);
 
    /* na zaklade predaneho typu ziskat velikost v bajtech */
    size = get_primitive_type_size(value_type);
 
    /* jmeno tridy lze ziskat na zaklade tagu z globalniho pole */
    index = object_class_tag - 1;
    class_name = class_names[index];
 
    /* vypiseme informace pouze pro dve testovaci tridy "Foo" a "Bar" */
    if (strcmp("Foo ", class_name)==0 || strcmp("Bar ", class_name)==0)
    {
        printf("Field of type %-7s in instance of class %shas size %1d byte%c and value ",
                primitive_type_str, class_name, size, size > 1 ? 's' : ' ');
        print_value(value_type, value);
    }
 
    /* spocitani statistickych informaci o obsazeni haldy */
    t_heap_stat *heap_stat = (t_heap_stat*)user_data;
    heap_stat->primitive_count++;
    heap_stat->total_size += size;
 
    /* pokracovat dal v prochazeni haldy */
    return JVMTI_VISIT_OBJECTS;
}

Takto upravený agent vypíše po svém připojení k běžícímu virtuálnímu stroji Javy následující zprávy:

Agent22: Agent_OnLoad
Agent22: JVM TI version is correct
Agent22: Got VM init event
Agent22: Got VM Death event
Agent22: Got data dump request
class java.io.BufferedOutputStream  has tag 1
class java.lang.Class$3  has tag 2
class java.util.Enumeration  has tag 3
class sun.misc.Version  has tag 4
...
...
...
class [Z has tag 373
class [B has tag 374
class [C has tag 375
class [I has tag 376
class [S has tag 377
class [J has tag 378
class [F has tag 379
class [D has tag 380
Field of type float   in instance of class Foo has size 4 bytes and value 3.141500
Field of type int     in instance of class Foo has size 4 bytes and value 42
Field of type boolean in instance of class Foo has size 1 byte  and value false
Field of type float   in instance of class Foo has size 4 bytes and value 2.717200
Field of type int     in instance of class Foo has size 4 bytes and value 6502
Field of type boolean in instance of class Foo has size 1 byte  and value true
Field of type long    in instance of class Bar has size 8 bytes and value 4
Field of type int     in instance of class Bar has size 4 bytes and value 3
Field of type short   in instance of class Bar has size 2 bytes and value 2
Field of type byte    in instance of class Bar has size 1 byte  and value 1
Field of type long    in instance of class Bar has size 8 bytes and value 4
Field of type int     in instance of class Bar has size 4 bytes and value 3
Field of type short   in instance of class Bar has size 2 bytes and value 2
Field of type byte    in instance of class Bar has size 1 byte  and value 1
Primitive values count: 5522 (fields only)
Total size:   21422 bytes
Agent22: Agent_OnUnload

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

Podobně jako ve třech předcházejících částech tohoto seriálu [57][58][59], byly i dnešní dva demonstrační příklady 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 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?
120na80.cz: Co je padesátkrát sladší než cukr?

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

Lupa.cz: Další Češi si nechali vložit do těla čip

Další Češi si nechali vložit do těla čip

Lupa.cz: Patička e-mailu závazná jako vlastnoruční podpis?

Patička e-mailu závazná jako vlastnoruční podpis?

Lupa.cz: Cimrman má hry na YouTube i vlastní doodle

Cimrman má hry na YouTube i vlastní doodle

120na80.cz: Na různou rýmu různá homeopatie

Na různou rýmu různá homeopatie

Podnikatel.cz: Poslanci chtějí sebrat majetek Bakalovi

Poslanci chtějí sebrat majetek Bakalovi

Lupa.cz: Proč jsou firemní počítače pomalé?

Proč jsou firemní počítače pomalé?

Lupa.cz: Jak se prodává firma za miliardu?

Jak se prodává firma za miliardu?

DigiZone.cz: Parlamentní listy: kde končí PR...

Parlamentní listy: kde končí PR...

Podnikatel.cz: Udělali jsme velkou chybu, napsal Čupr

Udělali jsme velkou chybu, napsal Čupr

DigiZone.cz: Ginx TV: pořad o počítačových hráčích

Ginx TV: pořad o počítačových hráčích

DigiZone.cz: Digi Slovakia zařazuje stanice SPI

Digi Slovakia zařazuje stanice SPI

Podnikatel.cz: Instalatér, malíř a elektrikář. "Vymřou"?

Instalatér, malíř a elektrikář. "Vymřou"?

Root.cz: Hořící telefon Samsung Note 7 zapálil auto

Hořící telefon Samsung Note 7 zapálil auto

Vitalia.cz: 5 chyb, které děláme při skladování potravin

5 chyb, které děláme při skladování potravin

Podnikatel.cz: Byla finanční manažerka, teď cvičí jógu

Byla finanční manažerka, teď cvičí jógu

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

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

Vitalia.cz: Voda z Vltavy před a po úpravě na pitnou

Voda z Vltavy před a po úpravě na pitnou

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: Technisat připravuje trojici DAB

Technisat připravuje trojici DAB