Obsah
1. Průchod haldou a výpis všech řetězců uložených na haldě
2. Callback funkce jvmtiStringPrimitiveValueCallback()
3. Demonstrační agent číslo 25 – výpis řetězců uložených na haldě
4. Statistika využití řetězců v jednoduchém testovacím příkladu (Test25)
5. Statistika využití řetězců v reálné aplikaci (Freemind)
6. Využití rozhraní JVM TI pro ladění (debugging) javovských aplikací
7. Zjištění všech atributů vybrané třídy
8. Demonstrační agent číslo 26 – výpis informací o všech atributech vybrané třídy
9. Zdrojové kódy obou demonstračních agentů a k nim příslušných testovacích příkladů
1. Průchod haldou a výpis všech řetězců uložených na haldě
V předchozí části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje Javy jsme se seznámili s tím, jakým způsobem jsou uloženy řetězce na haldě (heapu) virtuálního stroje (JVM). Dnes popis této poměrně důležité problematiky dokončíme, protože si ukážeme demonstračního JVM TI agenta, který dokáže vypsat všechny řetězce uložené na haldě – z těchto informací posléze budeme moci získat alespoň rámcovou představu o tom, jak paměťově náročná je práce s řetězci v jednoduchém demonstračním příkladu (kapitola 4) i v reálné desktopové aplikaci Freemind (kapitola 5). Toto měření však samozřejmě lze provést i pro ostatní javovské aplikace, například i pro aplikační servery a na nich nasazené (deploy) aplikace. Připomeňme si však nejdříve, jakým způsobem jsou řetězce na haldě uloženy. Řetězec je představován dvojicí objektů – instancí třídy String a instancí pole char[], které je atributem třídy String.
Pole char[] mohou být ve skutečnosti sdílená mezi větším množstvím řetězců, protože každá instance třídy String obsahuje další dva důležité atributy – délku řetězce (ve znacích) a offset od začátku pole char[]. Díky tomu je například možné implementovat metodu String.substring() takovým způsobem, že se neprovádí časově i paměťově náročné vytváření nového pole se znaky, ale prostě se vrátí nová instance třídy String se stejným atributem char[], ovšem s rozdílnými hodnotami atributů count a offset a samozřejmě též posledního atributu hash (proč je možné String.substring() takto implementovat je zřejmé – řetězce jsou v Javě neměnné neboli immutable). Každá instance třídy String zabere na haldě většinou 24 bajtů (platí pro 32bitové JVM i 64bitové JVM s relativně malou velikostí haldy), jak je to ostatně patrné i při pohledu na následující tabulku:
# | Velikost (B) | Struktura | Popis |
---|---|---|---|
1 | 8 | HEADER | hlavička objektu (přiřazena každému objektu na haldě) |
2 | 4 | int offset | atribut offset instance třídy String |
3 | 4 | int count | atribut count instance třídy String |
4 | 4 | int hash | atribut hash instance třídy String |
5 | 4 | char[] | reference na pole znaků |
(Potenciálně) sdílené pole char[] má na haldě samozřejmě proměnnou délku v závislosti na tom, kolik znaků řetězec skutečně obsahuje:
# | Velikost (B) | Struktura | Popis |
---|---|---|---|
1 | 8 | HEADER | hlavička objektu (přiřazena každému objektu na haldě) |
2 | 4 | int length | délka pole = délka řetězce ve znacích |
3 | length*2 | char[] | vlastní obsah řetězce (char má dva bajty) |
4 | 0 nebo 2 | PADDING | výplň, aby celková délka objektu byla dělitelná čtyřmi (někdy osmi) |
2. Callback funkce jvmtiStringPrimitiveValueCallback()
Nyní tedy již víme, jak jsou řetězce na haldě uloženy. Teoreticky již dokonce umíme všechny řetězce na haldě přečíst, protože by postačovalo projít instancemi všech tříd s využitím callback funkce jvmtiHeapReferenceCallback(), vybrat z nich všechny instance třídy String (což není těžké) a potom s využitím callback funkce jvmtiArrayPrimitiveValueCallback() projít všemi poli znaků (char[]) a z nich nějakým způsobem vyfiltrovat pouze ta pole, která jsou atributy instancí tříd String. Ovšem vzhledem k tomu, že se s řetězci pracuje skutečně velmi často a navíc i proto, že řetězce mají (jako objekty) v Javě poněkud zvláštní postavení, existuje v rozhraní JVM TI pro průchod řetězci speciální typ callback funkce jvmtiStringPrimitiveValueCallback(). Ukazatel na tuto callback funkci lze uložit do struktury jvmtiHeapCallbacks, kterou si v tomto seriálu již ukážeme skutečně naposledy:
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;
Ukazatel zapsaný do položky datové struktury jvmtiHeapCallbacks.jvmtiStringPrimitiveValueCallback musí obsahovat adresu callback funkce, jejíž hlavička má následující tvar:
jint JNICALL jvmtiStringPrimitiveValueCallback( jlong class_tag, jlong size, jlong* tag_ptr, const jchar* value, jint value_length, void* user_data);
Vidíme, že této callback funkci, která může být zavolána pro každý řetězec uložený na haldě, se při jejím zavolání předá šestice parametrů, z nichž některé již známe z dalších callback funkcí, ale některé parametry jsou zde zcela nové:
# | Typ parametru | Jméno parametru | Význam |
---|---|---|---|
1 | jlong | class_tag | tag přiřazený ke třídě, které je objekt instancí (bude to vždy třída String) |
2 | jlong | size | celková velikost alokovaná pro objekt na haldě (typicky 24 bajtů) |
3 | jlong* | tag_ptr | ukazatel na tag přiřazený k objektu |
4 | char* | value | obsah řetězce v Unicode (modifikovaný UTF-8) |
5 | jint | value_length | délka řetězce (odpovídá počtu 16bitových znaků) |
6 | void* | user_data | ukazatel na uživatelská data (má stejný význam, jaký již známe z minula) |
V prvním parametru se předává tag přiřazený ke třídě, jejíž instancí se právě na haldě prochází. V této callback funkci by se vždy mělo jednat o tag přiřazený ke třídě String, což si ostatně můžeme otestovat na demonstračním JVM TI agentovi. Ve druhém parametru je předávána velikost instance této třídy, což je (možná poněkud překvapivě) konstantní hodnota – většinou 24 bajtů. Proč tomu tak je bylo vysvětleno v předchozí kapitole. Užitečnější je pátý parametr obsahující skutečnou délku řetězce a samozřejmé též čtvrtý parametr s vlastním obsahem řetězce. Z toho vyplývá, že JVM TI dokáže automaticky spárovat instanci třídy String s příslušnou instancí pole char[], takže tuto relativně složitou činnost nemusíme dělat ručně.
3. Demonstrační agent číslo 25 – výpis řetězců uložených na haldě
Informace získané v předchozí kapitole využijeme ve dvacátém pátém demonstračním JVM TI agentovi, v němž je implementována speciální callback funkce pojmenovaná callback_for_each_string(), která musí být zaregistrována přes heap_callbacks.string_primitive_value_callback. Způsob registrace této callback funkce je ukázán na následujícím úryvku kódu, kde nejprve dojde k vlastní registraci callback funkce a následně se zahájí procházení haldou:
/* * Registrace callback funkce pro prochazeni haldou 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.string_primitive_value_callback = &callback_for_each_string; /* 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 o obsazeni haldy */ printf("String count: %d\n", heap_stat.arrays_count); printf("Total size: %ld bytes\n", heap_stat.total_size); }
Pro každý řetězec nalezený na haldě se vypíše délka tohoto řetězce (uvedená ve znacích), velikost objektu představujícího řetězec (jak jsme se již zmínili, bude zde většinou konstantní hodnota 24) a následně se řetězec vypíše, ale pro jistotu se budou vypisovat jen znaky s kódy 32 až 127, protože řetězec (resp. přesněji řečeno jednotlivé znaky v něm uložené) jsou reprezentovány v modifikovaném kódu UTF-8, s nímž se v čistém céčku pracuje dosti obtížně. V modifikovaném UTF-8 je znak uložen v jednom až třech bajtech, tj. délka je proměnná a oproti normálnímu kódování UTF-8 zde existuje omezení na maximálně tříbajtové sekvence. Navíc je zaručeno, že ASCII znaky 0×00 až 0×7f jsou reprezentovány jedním bajtem s ASCII kódem, zatímco všechny znaky s kódem 0×80 až 0×7ff jsou uloženy ve dvou bajtech a znaky s kódem 0×800 až 0×ffff ve třech bajtech – v tomto případě však mají všechny bajty nastaven nejvyšší bit na jedničku, tj. jejich hodnota je větší než 127.
Pokud tedy budeme v řetězci vyhledávat a následně tisknout pouze bajty s hodnotou menší než 128, je zaručeno, že se provede tisk pouze ASCII znaků a nikoli například „rozsypaného čaje“:
/* * Callback funkce volana pro kazdy retezec. */ static jint JNICALL callback_for_each_string( jlong class_tag, jlong size, jlong* tag_ptr, const jchar* value, jint value_length, void* user_data) { int i; printf("length: %4d chars, size: %ld bytes ", value_length, (long int)size); /* velmi zjednoduseny tisk *podmnoziny* Unicode znaku */ putchar('"'); for (i=0; i<value[i]; i++) { if (value[i] >= 32 && value[i] <= 127) { putchar(value[i]); } } putchar('"'); putchar('\n'); /* spocitani statistickych informaci o obsazeni haldy */ t_heap_stat *heap_stat = (t_heap_stat*)user_data; heap_stat->arrays_count++; heap_stat->total_size += size; /* pokracovat dale v prochazeni haldy */ return JVMTI_VISIT_OBJECTS; }
4. Statistika využití řetězců v jednoduchém testovacím příkladu (Test25)
Celý zdrojový kód demonstračního JVM TI agenta číslo 25 je, podobně jako i předchozí demonstrační agenti, dostupný v Mercurial repositáři. Zajímavé bude zjistit, kolik řetězců je uloženo na haldě virtuálního stroje Javy v případě, že je v JVM spuštěn pouze velmi jednoduchý a krátký příklad, jehož zdrojový kód je vypsán pod tímto odstavcem:
/** * Testovaci trida pouzita pro test dvacateho * pateho demonstracniho JVM TI agenta. */ public class Test25 { /** * Spusteni testu. */ public static void main(String[] args) { String s1 = "Hello world!"; String s2 = "http://www.root.cz"; String s3 = "abcdefghijklmnopqrstuvwxyz"; String s4 = "Příliš žluťoučký kůň úpěl ďábelské ódy"; } }
Vidíme, že v této testovací třídě jsou použity pouze čtyři řetězce, ovšem ve skutečnosti bude na haldě běžící JVM uloženo řetězců mnohem více. Po překladu JVM TI agenta i testovací třídy Test25 se virtuální stroj Javy spustí následujícím příkazem:
gcc -Wall -ansi -I/usr/lib/jvm/java-1.6.0-openjdk/include/ -shared -o libagent25.so agent25.c javac -g Test25.java
I když všichni očekáváme, že na haldě bude uloženo větší množství řetězců, může být výsledek i tak poměrně překvapivý:
Agent25: Agent_OnLoad Agent25: JVM TI version is correct Agent25: Got VM init event Agent25: Got VM Death event Agent25: 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 [Z has tag 371 class [B has tag 372 class [C has tag 373 class [I has tag 374 class [S has tag 375 class [J has tag 376 class [F has tag 377 class [D has tag 378 length: 15 chars, size: 24 bytes "Java heap space" length: 13 chars, size: 24 bytes "PermGen space" length: 37 chars, size: 24 bytes "Requested array size exceeds VM limit" length: 26 chars, size: 24 bytes "GC overhead limit exceeded" length: 9 chars, size: 24 bytes "/ by zero" length: 4 chars, size: 24 bytes "main" length: 4 chars, size: 24 bytes "main" length: 90 chars, size: 24 bytes "Method sun.misc.Unsafe.prefetchRead(Ljava/lang/Object;J" length: 108 chars, size: 24 bytes "Method sun.misc.Unsafe.copyMemory(Ljava/lang/Object;JLjava" ... ... ... length: 5 chars, size: 24 bytes "java." length: 4 chars, size: 24 bytes "main" length: 12 chars, size: 24 bytes "Hello world!" length: 18 chars, size: 24 bytes "http://www.root.cz" length: 26 chars, size: 24 bytes "abcdefghijklmnopqrstuvwxyz" length: 38 chars, size: 24 bytes "Pli luouk k pl belsk" Strings count: 927 Total size: 22248 bytes Agent25: Agent_OnUnload
Na haldě je tedy uloženo celkem 927 řetězců (toto číslo se může na vaší JVM o několik procent měnit) a velikost paměti obsazené pouze instancemi třídy String, nikoli tedy vlastními řetězci char[], dosahuje přibližné hodnoty dvacet jedna kilobajtů. V následující tabulce jsou vypsány další zajímavé statistické informace:
Počet řetězců | 927 |
Minimální délka řetězce | 0 znaků |
Maximální délka řetězce | 517 znaků |
Průměrná délka řetězce | 15,6 znaků |
Medián | 10 znaků |
Průměrná efektivita uložení | 44% |
Průměrná délka řetězce ani medián nám ovšem nedají zcela přesnou informaci o tom, jak dlouhé řetězce ve skutečnosti na haldě najdeme. Proto si navíc ještě ukážeme histogram, na jehož horizontální osu je nanesena délka řetězce (ve znacích) a na osu vertikální pak počet řetězců majících tuto délku. Histogram je zobrazen pro dva různé rozsahy horizontální osy: první zobrazuje frekvenci výskytu řetězců s délkou 0–600 znaků a druhý pouze řetězce s délkou 0–100 znaků:
Obrázek 1: Histogram s frekvencí výskytu řetězců s délkou 0–600 znaků.
Obrázek 2: Histogram s frekvencí výskytu řetězců s délkou 0–100 znaků.
5. Statistika využití řetězců v reálné aplikaci (Freemind)
Pro malé javovské programy lze předpokládat, že se na haldě budou nacházet především objekty vytvořené a využívané přímo virtuálním strojem Javy, což bude platit i o řetězcích. O tom jsme se ostatně mohli přesvědčit v předchozím případě, kdy javovský program obsahoval pouze čtyři explicitně zadané řetězce, zatímco na haldě jich bylo uloženo celkem 927. Přitom se většinou jednalo o velmi krátké řetězce s průměrnou délkou 15,6 znaků a s mediánem dokonce pouhých 10 znaků. Zkusme tedy využít našeho JVM TI agenta pro získání statistiky o řetězcích pro reálnou aplikaci. Bude se jednat o poměrně známý desktopový program Freemind sloužící pro tvorbu takzvaných myšlenkových map. Jedná se o středně velkou aplikaci, takže získané informace by mohly být poměrně zajímavé.
Obrázek 3: Screenshot programu Freemind ve chvíli, kdy je virtuální stroj monitorován JVM TI agentem číslo 25.
Freemind se spouští pomocí skriptu, v němž se nastavují cesty k různým Java archivům. Tento skript bylo pro potřeby měření JVM TI agentem nutné upravit do následující podoby (která je ovšem závislá na konkrétních adresářích, kde jsou uloženy Java archivy):
CLASSPATH=\ /usr/share/java/SimplyHTML.jar:\ /usr/share/java/gnu-regexp.jar:\ /usr/share/java/jibx-run.jar:\ /usr/share/java/xpp3.jar:\ /usr/share/freemind/lib/bindings.jar:\ /usr/share/freemind/lib/freemind.jar:\ /usr/share/java/commons-lang.jar:\ /usr/share/java/forms.jar java -agentpath:./libagent25.so -Xmx256M -Dfreemind.base.dir="/usr/share/freemind" -cp "${CLASSPATH}" freemind.main.FreeMindStarter
Po spuštění tohoto skriptu dojde k zobrazení grafického uživatelského rozhraní Freemindu a je možné s ním normálně pracovat. Až teprve při ukončování činnosti virtuálního stroje se zavolá callback funkce v JVM TI agentovi, která nastartuje procházení všemi řetězci na haldě:
Checking Java Version... Agent25: Agent_OnLoad Agent25: JVM TI version is correct Agent25: Got VM init event Agent25: Got VM Death event Agent25: Got data dump request class freemind.modes.common.GotoLinkNodeAction has tag 1 class sun.awt.image.IntegerComponentRaster has tag 2 class javax.swing.plaf.PanelUI has tag 3 class sun.awt.MostRecentKeyValue has tag 4 class javax.swing.plaf.basic.BasicHTML$BasicHTMLViewFactory has tag 5 class java.util.TaskQueue has tag 6 class java.util.Currency has tag 7 class sun.awt.X11.XNETProtocol has tag 8 class sun.awt.X11.XProtocolConstants has tag 9 ... ... ... class [[S has tag 3405 class [[[S has tag 3406 class [J has tag 3407 class [[J has tag 3408 class [F has tag 3409 class [[F has tag 3410 class [D has tag 3411 class [[D has tag 3412 length: 31 chars, size: 24 bytes "End paint in 213. Mean time:247" length: 0 chars, size: 24 bytes " " length: 9 chars, size: 24 bytes "18.1.2013" length: 8 chars, size: 24 bytes "23:38:41" length: 0 chars, size: 24 bytes " " length: 4 chars, size: 24 bytes "cs__" length: 97 chars, size: 24 bytes "18.1.2013 23:38:41 freemind.view.mindmapview.MapView" length: 38 chars, size: 24 bytes "Java_java_awt_AWTEvent_nativeSetSource" length: 58 chars, size: 24 bytes "Java_sun_awt_X11_XWindow_haveCurrentX11InputMethodInstance" length: 51 chars, size: 24 bytes "Java_sun_awt_X11_XWindow_x11inputMethodLookupString" length: 20 chars, size: 24 bytes "The X.Org Foundation" length: 40 chars, size: 24 bytes "Java_sun_awt_X11_XlibWrapper_IsKeypadKey" length: 17 chars, size: 24 bytes "type = FocusOut, " length: 31 chars, size: 24 bytes "type = FocusOut, serial = 695, " ... ... ... length: 10 chars, size: 24 bytes "US/Central" length: 10 chars, size: 24 bytes "US/Eastern" length: 9 chars, size: 24 bytes "US/Hawaii" length: 17 chars, size: 24 bytes "US/Indiana-Starke" length: 15 chars, size: 24 bytes "US/East-Indiana" length: 11 chars, size: 24 bytes "US/Michigan" length: 11 chars, size: 24 bytes "US/Mountain" length: 10 chars, size: 24 bytes "US/Pacific" length: 14 chars, size: 24 bytes "US/Pacific-New" length: 8 chars, size: 24 bytes "US/Samoa" length: 3 chars, size: 24 bytes "VST" length: 4 chars, size: 24 bytes "W-SU" length: 4 chars, size: 24 bytes "Zulu" length: 17 chars, size: 24 bytes "filter_conditions" Strings count: 29737 Total size: 713688 bytes Agent25: Agent_OnUnload
Pro tuto průměrnou desktopovou aplikaci se tedy na haldě vytvořilo necelých třicet tisíc(!) řetězců a pouze instance třídy String zabraly téměř 700 kilobajtů (do této velikosti nejsou počítána pole char[]). V následující tabulce jsou zobrazeny další zajímavé statistické informace:
Počet řetězců | 29737 |
Minimální délka řetězce | 0 znaků |
Maximální délka řetězce | 10387 znaků |
Průměrná délka řetězce | 23,7 znaků |
Medián | 16 znaků |
Průměrná efektivita uložení | 55 % |
Průměrná délka řetězce i medián se oproti první testovací aplikaci zvýšily, což znamená, že se zlepšila i efektivita uložení řetězců (poměr mezi obsazením haldy znaky dělený celkovým obsazením instancemi tříd String a jejími atributy char[]). Podívejme se taktéž na histogramy. První histogram opět ukazuje frekvenci řetězců s délkami 0 až 600 znaků a druhý pouze frekvence řetězců s délkou 0 až 100 znaků.
Obrázek 4: Histogram s frekvencí výskytu řetězců s délkou 0–600 znaků.
Obrázek 5: Histogram s frekvencí výskytu řetězců s délkou 0–100 znaků.
Jen pro doplnění je ještě vhodné k oběma histogramům přidat minule vysvětlený graf, na jehož horizontální osu je vynesena délka řetězce a na osu vertikální (pravá osa) pak efektivita uložení takto dlouhého řetězce v paměti:
Obrázek 6: Efektivita uložení řetězců s různou délkou.
6. Využití rozhraní JVM TI pro ladění (debugging) javovských aplikací
Rozhraní JVM TI je možné využít i pro ladění javovských aplikací – jinými slovy je možné s využitím JVM TI naprogramovat i poměrně složitý debugger, i když je na tomto místě nutné říci, že výhodnější a především jednodušší bývá v tomto případě použití rozhraní JDWP, které je přímo určené právě pro implementaci debuggerů. Pokud se smíříme s tím, že JVM TI je nízkoúrovňové rozhraní, nabízí nám v oblasti ladění ve skutečnosti poměrně širokou funkcionalitu, například:
- Zjištění vzniku zachycované či nezachycované výjimky (to již umíme).
- Zjištění zachycení výjimky (taktéž již umíme).
- Získání seznamu všech načtených tříd (-//-).
- Přečtení všech objektů, polí i primitivních hodnot na haldě (-//-).
- Zjištění zápisu do atributu.
- Zjištění pouhého čtení hodnoty atributu.
- Zachycení vstupu do metody.
- Zachycení výstupu z metody.
- Nastavení breakpointu.
- Vynucení výskoku z funkce s vybranou návratovou hodnotou.
- Krokování po jednotlivých instrukcích.
7. Zjištění všech atributů vybrané třídy
V následujícím textu se budeme zabývat především body číslo 5 a 6, tj. způsobem detekce zápisu do atributu vybrané třídy/objektu a způsobem detekce čtení tohoto atributu. Tuto užitečnou funkci nám nabízí prakticky všechny moderní debuggery (příkaz watch…). S využitím funkcionality nabízené rozhraním JVM TI je možné k libovolnému atributu třídy či objektu zaregistrovat callback funkci zavolanou ve chvíli, kdy je hodnota tohoto atributu změněna (je proveden zápis) či kdy je naopak jen přečtena aktuální hodnota tohoto atributu. Nicméně při registraci těchto callback funkcí je zapotřebí znát identifikátor tohoto atributu reprezentovaný hodnotou typu jfieldID, kterou musíme nějakým způsobem získat. Nejprve si ukážeme, jak je možné získat informace o všech atributech vybrané javovské třídy, protože přesně tato činnost se provádí v debuggeru – tam je taktéž nutné vybrat třídu/objekt a její/jeho atribut, který se má sledovat.
Demonstrační agent číslo 26 získá a vypíše jména, signatury a modifikátory všech atributů třídy Test26 (a samozřejmě i všech případných instancí této třídy). Základem funkcionality tohoto agenta je funkce callback_on_info_request() zavolaná ve chvíli ukončování práce JVM. Ve skutečnosti je však možné tuto funkci zavolat naopak při inicializaci JVM, v této chvíli je to jedno. V callback funkci callback_on_info_request() se získá seznam všech načtených tříd a ve smyčce se tímto seznamem prochází. Ve chvíli, kdy jméno třídy odpovídá zadanému jménu „Test26“ je zavolána funkce print_attributes_info(), které se předá identifikátor příslušné třídy:
/* * Callback funkce zavolana pri cteni informaci o atributech vybrane tridy. */ static void JNICALL callback_on_info_request(jvmtiEnv *jvmti_env) { jvmtiError error; jint class_count; jclass *class_array; int i; MSG("Got data dump request"); error = (*jvmti_env)->GetLoadedClasses(jvmti_env, &class_count, &class_array); check_jvmti_error(jvmti_env, error, "get loaded classes"); for (i=0; i < class_count; i++) { char *class_name_ptr; char *updated_class_name_ptr; /* ziskat jmeno tridy */ 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 jmeno tridy */ updated_class_name_ptr = update_class_name(class_name_ptr, ';'); /* nasli jsme nasi testovaci tridu? */ if (strcmp(TEST_CLASS_NAME, updated_class_name_ptr) == 0) { printf("Field\t%-20s\t%-25s\tModifiers\n", "Name", "Signature"); print_attributes_info(jvmti_env, class_array[i]); } /* dealokace pameti po GetClassSignature() */ error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char *)class_name_ptr); check_jvmti_error(jvmti_env, error, "deallocate class name"); } }
Ve funkci print_attributes_info() se s využitím JVM TI funkce GetClassFields() získá pole obsahující identifikátory všech atributů, které jsou v dané třídě deklarovány. Toto pole obsahuje prvky typu jfieldID a JVM TI funkce GetClassFields() kromě tohoto pole vrátí i jeho délku, čehož se využije v programové smyčce, která všemi hodnotami jfieldID prochází a pro každou získanou hodnotu zavolá funkci print_info_for_attribute():
/* * Zjisteni a nasledny tisk informaci o atributech tridy. */ static void JNICALL print_attributes_info(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 vypsat o nich informace */ for (i = 0; i < field_count; i++) { print_info_for_attribute(jvmti_env, class, fields_array[i], i); } } /* 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"); }
Do funkce print_info_for_attribute() jsou předány dvě důležité informace – hodnoty jclass a jfieldID, které jednoznačně určují příslušný atribut třídy. Pro získání jména a signatury atributu se použije JVM TI funkce nazvaná GetFieldName() a pro přečtení modifikátorů (viz další text) pak funkce nazvaná GetFieldModifiers(). Použití obou zmíněných JVM TI funkcí je jednoduché, pouze nesmíme zapomenout na dealokaci řetězců vrácených z GetFieldName():
/* * Tisk informaci o vybranem atributu tridy. */ static void print_info_for_attribute(jvmtiEnv *jvmti_env, jclass class, jfieldID field, int i) { jvmtiError error; char *name; char *signature; int modifiers; error = (*jvmti_env)->GetFieldName(jvmti_env, class, field, &name, &signature, NULL); check_jvmti_error(jvmti_env, error, "get field name"); error = (*jvmti_env)->GetFieldModifiers(jvmti_env, class, field, &modifiers); check_jvmti_error(jvmti_env, error, "get field modifiers"); /* zakladni informace */ printf("%d\t%-20s\t%-25s\t", i, name, signature); /* tisk pripadnych modifikatoru */ print_modifiers(modifiers); putchar('\n'); /* dealokace pameti */ error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)name); check_jvmti_error(jvmti_env, error, "deallocate field name"); error = (*jvmti_env)->Deallocate(jvmti_env, (unsigned char*)signature); check_jvmti_error(jvmti_env, error, "deallocate field signature"); }
Nyní nám již zbývá pouze vytisknout všechny případné modifikátory atributu. Tyto modifikátory jsou uloženy v jediné celočíselné hodnotě jako bitové příznaky. My již vlastně hodnoty těchto příznaků známe, protože jsou stejné, jako příznaky používané v bajtkódu JVM (části 18 až 31 tohoto seriálu):
/* * Priznaky tridy, rozhrani ci datove polozky */ enum { ACC_PUBLIC = 0x0001, /* verejna trida/rozhrani/datova polozka */ ACC_PRIVATE = 0x0002, /* privatni datova polozka dostupna pouze uvnitr tridy */ ACC_PROTECTED = 0x0004, /* chranena datova polozka dostupna uvnitr tridy a z pripadne odvozene tridy */ ACC_STATIC = 0x0008, /* staticka datova polozka */ ACC_FINAL = 0x0010, /* finalni trida ci datova polozka */ ACC_VOLATILE = 0x0040, /* priznak volatile - hodnota se vzdy precte ci zapise do pameti */ ACC_TRANSIENT = 0x0080, /* priznak transient - nebude se zpracovavat pri (de)serializaci */ ACC_INTERFACE = 0x0200, /* rozliseni trida/rozhrani */ ACC_ABSTRACT = 0x0400, /* abstraktni trida */ ACC_SYNTHETIC = 0x1000, /* synteticka trida ci datova polozka, nema svuj protejsek ve zdrojovem kodu */ ACC_ANNOTATION = 0x2000, /* anotace */ ACC_ENUM = 0x4000, /* vycet */ };
Se znalostí číselného kódu vráceného JVM TI funkcí GetFieldModifiers() a výše vypsaného výčtu s bitovými maskami je možné vypsat všechny modifikátory příslušného atributu či jejich různé kombinace například následujícím způsobem:
/* * Tisk informaci o modifikatorech atributu. */ static void print_modifiers(int modifiers) { if (modifiers & ACC_PUBLIC) fputs(" public", stdout); if (modifiers & ACC_PRIVATE) fputs(" private", stdout); if (modifiers & ACC_PROTECTED) fputs(" protected", stdout); if (modifiers & ACC_STATIC) fputs(" static", stdout); if (modifiers & ACC_FINAL) fputs(" final", stdout); if (modifiers & ACC_ABSTRACT) fputs(" abstract", stdout); if (modifiers & ACC_SYNTHETIC) fputs(" synthetic", stdout); if (modifiers & ACC_TRANSIENT) fputs(" transient", stdout); if (modifiers & ACC_INTERFACE) fputs(" interface", stdout); if (modifiers & ACC_ANNOTATION) fputs(" annotation", stdout); if (modifiers & ACC_ENUM) fputs(" enum", stdout); if (modifiers & ACC_VOLATILE) fputs(" volatile", stdout); }
Poznámka: funkce fputs() je zde použita pouze z toho důvodu, že nevkládá do standardního výstupu znak pro konec řádku, jiný důvod pro provádění této „magie“ neexistuje :-)
8. Demonstrační agent číslo 26 – výpis informací o všech atributech vybrané třídy
Pro vyzkoušení činnosti dvacátého šestého demonstračního JVM TI agenta, jehož zdrojový kód je opět uložen v Mercurial repositáři (viz též kapitola číslo 9) bude tento agent spuštěn s testovací třídou Test26, v níž je deklarováno poměrně velké množství různých atributů. Jedná se o atributy primitivního datového typu, pole primitivních datových typů, reference na objekty i pole referencí na objekty. Navíc jsou u některých atributů použity různé modifikátory, ať již se jedná o modifikátory přístupu (public/protected/private), tak i modifikátory static (třídní atribut), transient (atribut nebude serializován), volatile (nebude se provádět žádná optimalizace při čtení a zápisu hodnoty atributu) a final (jedná se o konstantní hodnotu). Tyto atributy lze samozřejmě různě kombinovat, což je taktéž v testovací třídě použito:
/** * Testovaci trida pouzita pro test dvacateho * sesteho demonstracniho JVM TI agenta. */ public class Test26 { /* Zakladni datove typy a obalove tridy */ int i; Integer I; float f; Float F; /* Pole, tridy a rozhrani */ int[] pole; boolean[][] matice; String retezec; String[] moc_retezcu; Test26 instance; Comparable comparable; /* Ruzne modifikatory */ public int public_int; private int private_int; protected int protected_int; int proste_jen_int; transient int transient_int; volatile int volatile_int; final int final_int = 0; static int static_int; /* Kombinace modifikatoru */ public static final int public_static_final_int = 42; private transient java.awt.Color color; protected volatile static java.util.Date date; /** * Spusteni testu. */ public static void main(String[] args) { } }
Po spuštění virtuálního stroje Javy společně s JVM TI agentem:
java -agentpath:./libagent26.so Test26 2> /dev/null
Získáme následující výpis, který přesně odpovídá deklaraci atributů v testovací třídě Test26:
Agent26: Agent_OnLoad Agent26: JVM TI version is correct Agent26: Got VM init event Agent26: Got VM Death event Agent26: Got data dump request Field Name Signature Modifiers 0 i I 1 I Ljava/lang/Integer; 2 f F 3 F Ljava/lang/Float; 4 pole [I 5 matice [[Z 6 retezec Ljava/lang/String; 7 moc_retezcu [Ljava/lang/String; 8 instance LTest26; 9 comparable Ljava/lang/Comparable; 10 public_int I public 11 private_int I private 12 protected_int I protected 13 proste_jen_int I 14 transient_int I transient 15 volatile_int I volatile 16 final_int I final 17 static_int I static 18 public_static_final_int I public static final 19 color Ljava/awt/Color; private transient 20 date Ljava/util/Date; protected static volatile Agent26: Agent_OnUnload
V následující části tohoto seriálu si ukážeme, jak lze takto získané informace dále využít při tvorbě jednoduchého „debuggeru“.
9. Zdrojové kódy obou demonstračních agentů a k nim příslušných testovacích příkladů
Podobně jako v pěti 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.org/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:
10. Odkazy na Internetu
- The JVM Tool Interface (JVM TI): How VM Agents Work
http://www.oracle.com/technetwork/articles/javase/jvm-ti-141370.html - JVM Tool Interface Version 1.2
http://docs.oracle.com/javase/7/docs/platform/jvmti/jvmti.html - Creating a Debugging and Profiling Agent with JVMTI
http://www.oracle.com/technetwork/articles/javase/jvmti-136367.html - JVM TI (Wikipedia)
http://en.wikipedia.org/wiki/JVM_TI - IBM JVMTI extensions
http://publib.boulder.ibm.com/infocenter/realtime/v2r0/index.jsp?topic=%2Fcom.ibm.softrt.doc%2Fdiag%2Ftools%2Fjvmti_extensions.html - An empirical study of Java bytecode programs
http://www.mendeley.com/research/an-empirical-study-of-java-bytecode-programs/ - Java quick guide: JVM Instruction Set (tabulka všech instrukcí JVM)
http://www.mobilefish.com/tutorials/java/java_quickguide_jvm_instruction_set.html - The JVM Instruction Set
http://mpdeboer.home.xs4all.nl/scriptie/node14.html - Control Flow in the Java Virtual Machine
http://www.artima.com/underthehood/flowP.html - The class File Format
http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html - javap – The Java Class File Disassembler
http://docs.oracle.com/javase/1.4.2/docs/tooldocs/windows/javap.html - javap-java-1.6.0-openjdk(1) – Linux man page
http://linux.die.net/man/1/javap-java-1.6.0-openjdk - Using javap
http://www.idevelopment.info/data/Programming/java/miscellaneous_java/Using_javap.html - Examine class files with the javap command
http://www.techrepublic.com/article/examine-class-files-with-the-javap-command/5815354 - Bytecode Engineering
http://book.chinaunix.net/special/ebook/Core_Java2_Volume2AF/0131118269/ch13lev1sec6.html