Obsah
1. Dokončení předchozí části seriálu – zjištění pořadí atributu v rámci třídy
2. Přečtení pořadí atributu ve třídě z unie jvmtiHeapReferenceInfo
3. Demonstrační agent číslo 23 – výpis pořadí všech atributů nalezených na haldě
4. Přečtení informací o polích primitivních datových typů uložených na haldě
5. Demonstrační agent číslo 24 – výpis informací o polích primitivních datových typů
6. Proč velikost paměti obsazené polem neodpovídá objemu paměti vyhrazené pro prvky pole?
7. Řetězce na haldě – aneb specifický typ objektů
8. Struktura objektu představujícího řetězec
9. Efektivita uložení řetězců na haldě
10. Výpočet efektivity uložení řetězců na haldě
11. Zdrojové kódy všech tří agentů a k nim příslušných testovacích příkladů
1. Dokončení předchozí části seriálu – zjištění pořadí atributu v rámci třídy
V dnešní části seriálu o programovacím jazyce Java i o vlastnostech virtuálního stroje Javy nejprve dokončíme téma, kterému jsme se již začali věnovat minule. Jedná se o výpis typů a hodnot atributů uložených na haldě. Posléze si řekneme, jakým způsobem je možné získat informace o polích (primitivních typů) uložených na haldě a následně se budeme zabývat způsobem uložení řetězců, protože se jedná o poměrně důležité téma, jelikož způsob práce s řetězci může mnohdy velmi významným způsobem ovlivnit jak výkonnost javovských aplikací, tak i jejich paměťovou náročnost. Vraťme se nejdříve ke dvěma demonstračním JVM TI agentům popsaným minule. V těchto agentech se používala callback funkce nazvaná callback_for_primitive_values(), která se volala pro všechny atributy objektů uložených na haldě. Přitom se jednalo o atributy, jejichž typ patřil mezi primitivní typy Javy – boolean, byte, short, int, long, char, float či double. Hlavička zmíněné callback funkce vypadá 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)
Již víme, jak je možné z údajů předaných této callback funkci získat jméno příslušné třídy (jejíž instance jsou zkoumány), typ atributu i hodnotu, která je v tomto atributu uložena. Prozatím však ještě nevíme, jakým způsobem je možné zjistit pořadí atributu v rámci třídy (či její instance). To je další a v podstatě již poslední informace, kterou můžeme relativně snadno získat, tentokrát s využitím prvního parametru kind a především pak parametru druhého info. Podrobnosti si řekneme v navazující kapitole.
2. Přečtení pořadí atributu ve třídě z unie jvmtiHeapReferenceInfo
Při volání callback funkce callback_for_primitive_values() je v prvním parametru nazvaném kind předána informace o tom, jaký prvek uložený na haldě je právě prozkoumáván. Datový typ parametru kind je jvmtiHeapReferenceKind a pohledem do hlavičkového souboru jvmti.h (či do dokumentace, ale zdrojový kód bývá často tou nejlepší dokumentací :-) lze velmi snadno zjistit, že se jedná o výčet s celkem sedmnácti deklarovanými hodnotami:
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;
Nás však v tento okamžik zajímají pouze hodnoty primitivních datových typů uložených na haldě, pro něž se při volání callback funkce v parametru kind vždy předává hodnota JVMTI_HEAP_REFERENCE_FIELD, čemuž ostatně odpovídá i podmínka zapsaná uvnitř těla callback funkce:
/* zajimaji nas pouze nestaticke atributy objektu */ if (kind != JVMTI_HEAP_REFERENCE_FIELD) return JVMTI_VISIT_OBJECTS;
Důležitější, zajímavější a současně i poněkud komplikovanější je však druhý parametr naší callback funkce nazvaný info, jehož typ je jvmtiHeapReferenceInfo. Pro zjištění, o jaký datový typ se ve skutečnosti jedná, budeme muset opět prozkoumat hlavičkový soubor jvmti.h, z něhož lze vyčíst, že jde o unii, která může v daný okamžik obsahovat jednu ze šesti (ve skutečnosti však jen pěti) datových struktur:
union jvmtiHeapReferenceInfo { jvmtiHeapReferenceInfoField field; jvmtiHeapReferenceInfoArray array; jvmtiHeapReferenceInfoConstantPool constant_pool; jvmtiHeapReferenceInfoStackLocal stack_local; jvmtiHeapReferenceInfoJniLocal jni_local; jvmtiHeapReferenceInfoReserved other; };
Pro úplnost si uveďme deklarace všech šesti datových struktur ukládaných do unie, i když nás ve skutečnosti bude zajímat pouze struktura jediná – jvmtiHeapReferenceInfoField:
struct jvmtiHeapReferenceInfoField { jint index; /* zde je uloženo pořadí atributu v rámci třídy/objektu */ };
struct jvmtiHeapReferenceInfoArray { jint index; };
struct jvmtiHeapReferenceInfoConstantPool { jint index; };
struct jvmtiHeapReferenceInfoStackLocal { jlong thread_tag; jlong thread_id; jint depth; jmethodID method; jlocation location; jint slot; };
struct jvmtiHeapReferenceInfoJniLocal { jlong thread_tag; jlong thread_id; jint depth; jmethodID method; };
struct jvmtiHeapReferenceInfoReserved { jlong reserved1; jlong reserved2; jlong reserved3; jlong reserved4; jlong reserved5; jlong reserved6; jlong reserved7; jlong reserved8; };
3. Demonstrační agent číslo 23 – výpis pořadí všech atributů nalezených na haldě
Další postup při vylepšování demonstračního JVM TI agenta je již poměrně snadný; stačí jen vědět, že v případě, že je v prvním parametru callback funkce callback_for_primitive_values() předána hodnota JVMTI_HEAP_REFERENCE_FIELD, pak je v unii info vždy uložena datová struktura jvmtiHeapReferenceInfoField, v níž je umístěn jediný prvek – kýžený index atributu v rámci třídy či objektu (instance třídy). Odkaz na celý zdrojový kód nové verze demonstračního JVM TI agenta je uveden v jedenácté kapitole, zde si pouze ukážeme upravenou formu callback funkce volané pro všechny hodnoty primitivních datových typů uložených na haldě:
/* * 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; int field_no; char *primitive_type_str; char *class_name; /* struktura obsahujici podrobnejsi informace o atributu */ jvmtiHeapReferenceInfoField *reference_info_field; /* 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); /* ziskat poradi atributu v tride */ /* nutno pretypovat, protoze jvmtiHeapReferenceInfo je ve skutecnosti unie */ reference_info_field = (jvmtiHeapReferenceInfoField*)info; field_no = reference_info_field->index; /* 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 #%2d of type %-7s in instance of class %shas size %1d byte%c and value ", field_no, 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; }
Po překladu a spuštění tohoto demonstračního příkladu skripty uvedenými opět v jedenácté kapitole získáme následující výstup:
Agent23: Agent_OnLoad Agent23: JVM TI version is correct Agent23: Got VM init event Agent23: Got VM Death event Agent23: 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 java.util.Collections$EmptyList has tag 5 class sun.misc.ExtensionDependency has tag 6 class java.nio.HeapByteBuffer has tag 7 ... ... ... 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 # 2 of type float in instance of class Foo has size 4 bytes and value 3.141500 Field # 1 of type int in instance of class Foo has size 4 bytes and value 42 Field # 0 of type boolean in instance of class Foo has size 1 byte and value false Field # 2 of type float in instance of class Foo has size 4 bytes and value 2.717200 Field # 1 of type int in instance of class Foo has size 4 bytes and value 6502 Field # 0 of type boolean in instance of class Foo has size 1 byte and value true Field # 3 of type long in instance of class Bar has size 8 bytes and value 4 Field # 2 of type int in instance of class Bar has size 4 bytes and value 3 Field # 1 of type short in instance of class Bar has size 2 bytes and value 2 Field # 0 of type byte in instance of class Bar has size 1 byte and value 1 Field # 3 of type long in instance of class Bar has size 8 bytes and value 4 Field # 2 of type int in instance of class Bar has size 4 bytes and value 3 Field # 1 of type short in instance of class Bar has size 2 bytes and value 2 Field # 0 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 Agent23: Agent_OnUnload
Nyní si můžete porovnat zdrojový kód testovacích tříd Foo a Bar s informacemi vypsanými výše…
4. Přečtení informací o polích primitivních datových typů uložených na haldě
Nyní již víme, jakým způsobem je možné vypsat všechny objekty uložené na haldě i všechny „jednoduché“ atributy objektů – ať již se jedná o atributy objektového typu (=reference) či o atributy primitivního datového typu. Ovšem ještě nám zbývá si říci, jak lze získat informace o polích uložených na haldě, a to včetně polí znaků, protože právě s využitím polí znaků (pevné délky) jsou implementovány řetězce, které hrají v mnoha typech javovských aplikací téměř klíčovou roli. Nejdříve si popíšeme způsob čtení informací o polích, jejichž prvky jsou primitivního datového typu, tj. polí vytvořených například takto:
byte[] b = new byte[length]; short[] s = new short[length]; int[] i = new int[length]; long[] l = new long[length]; char[] c = new char[length]; float[] f = new float[length]; double[] d = new double[length]; boolean[] z = new boolean[length];
Poznámka: vícerozměrná pole se v Javě zpracovávají jako pole polí, podobně jako je tomu například v céčku a mnoha dalších programovacích jazycích.
Při průchodu poli uloženými na haldě se používá speciální forma callback funkce, která je ve struktuře jvmtiHeapCallbacks představována ukazatelem array_primitive_value_callback:
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;
Registrace této callback funkce se provádí naprosto stejným způsobem, jako registrace callback funkcí volaných pro objekty i pro atributy primitivních datových typů; bude se však lišit počet i typ parametrů předávaných této callback funkci (tu si ukážeme v navazující kapitole). V kódu níže je ukázán způsob registrace této callback funkce s následným zahájením průchodu haldou:
/* * 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, 0}; /* naplnit strukturu heap_callbacks jednou callback funkci */ (void)memset(&heap_callbacks, 0, sizeof(heap_callbacks)); heap_callbacks.array_primitive_value_callback = &callback_for_array_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("Arrays count: %d\n", heap_stat.arrays_count); printf("Data size: %ld bytes\n", heap_stat.data_size); printf("Total size: %ld bytes\n", heap_stat.total_size); }
5. Demonstrační agent číslo 24 – výpis informací o polích primitivních datových typů
Callback funkci zavolané při nalezení pole s prvky primitivního datového typu se předává celkem sedm parametrů, z nichž některé již známe z jiných typů callback funkcí, ovšem další parametry budou zcela nové:
# | Typ parametru | Jméno parametru | Význam |
---|---|---|---|
1 | jlong | class_tag | tag přiřazený ke třídě, které je objekt instancí |
2 | jlong | size | celková velikost alokovaná pro pole na haldě |
3 | jlong* | tag_ptr | ukazatel na tag přiřazený k objektu |
4 | jint | element_count | počet prvků pole |
5 | jvmtiPrimitiveType | element_type | typ hodnoty (byte, int, float…) prvků pole |
6 | void* | element | obsah pole (hodnoty prvků pole) |
7 | void* | user_data | ukazatel na uživatelská data (má stejný význam, jaký již známe z minula) |
Při průchodu haldou nás bude zajímat především počet prvků pole, typ prvků pole a velikost paměti alokované pro pole (proč je tato paměť větší, než by odpovídalo počtu prvků pole, si řekneme v dalším textu):
/* * Callback funkce volana pro kazde pole primitivnich * datovych typu ulozene na halde. */ static jint JNICALL callback_for_array_values( jlong class_tag, jlong size, jlong* tag_ptr, jint element_count, jvmtiPrimitiveType element_type, const void* elements, void* user_data) { char *primitive_type_str; char *class_name; int index; long data_size; /* na zaklade predaneho typu ziskat jmeno typu jako retezec */ primitive_type_str = get_primitive_type_str(element_type); /* jmeno tridy lze ziskat na zaklade tagu z globalniho pole */ index = class_tag - 1; class_name = class_names[index]; /* ziskat objem zabrany jen daty, nikoli vyplni */ data_size = element_count * get_primitive_type_size(element_type); /* "magicke" delky pouzite v testovaci tride */ if (element_count == 1 || element_count == 1234 || element_count == 10000) { printf("%s %s[%d] with size %ld(%ld) bytes\n", class_name, primitive_type_str, element_count, data_size, (long int)size); } /* 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; heap_stat->data_size += data_size; /* pokracovat dal v prochazeni haldy */ return JVMTI_VISIT_OBJECTS; }
Kompletní zdrojový kód demonstračního JVM TI agenta je opět uveden v kapitole číslo 11.
6. Proč velikost paměti obsazené polem neodpovídá objemu paměti vyhrazené pro prvky pole?
Po spuštění demonstračního agenta číslo 24 získáme následující (poněkud zkrácený) výpis:
Agent24: Agent_OnLoad Agent24: JVM TI version is correct Agent24: Got VM init event Agent24: Got VM Death event Agent24: 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 java.util.Collections$EmptyMap has tag 5 class sun.misc.ExtensionDependency has tag 6 class java.nio.HeapByteBuffer has tag 7 class java.util.Arrays has tag 8 class sun.misc.JavaIOAccess has tag 9 class java.util.IdentityHashMap has tag 10 class java.net.URLStreamHandlerFactory has tag 11 class Foo has tag 12 ... ... ... 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 [D has tag 379 [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [B byte[1] with size 1(16) bytes [C char[1] with size 2(16) bytes [B byte[1] with size 1(16) bytes [B byte[1] with size 1(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [B byte[1] with size 1(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [B byte[1] with size 1(16) bytes [S short[1] with size 2(16) bytes [I int[1] with size 4(16) bytes [J long[1] with size 8(24) bytes [C char[1] with size 2(16) bytes [F float[1] with size 4(16) bytes [D double[1] with size 8(24) bytes [Z boolean[1] with size 1(16) bytes [B byte[1234] with size 1234(1248) bytes [S short[1234] with size 2468(2480) bytes [I int[1234] with size 4936(4952) bytes [J long[1234] with size 9872(9888) bytes [C char[1234] with size 2468(2480) bytes [F float[1234] with size 4936(4952) bytes [Z boolean[1234] with size 1234(1248) bytes [D double[1234] with size 9872(9888) bytes [B byte[10000] with size 10000(10016) bytes [S short[10000] with size 20000(20016) bytes [I int[10000] with size 40000(40016) bytes [J long[10000] with size 80000(80016) bytes [C char[10000] with size 20000(20016) bytes [F float[10000] with size 40000(40016) bytes [D double[10000] with size 80000(80016) bytes [Z boolean[10000] with size 10000(10016) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes [C char[1] with size 2(16) bytes Arrays count: 2494 Data size: 610459 bytes Total size: 648752 bytes Agent24: Agent_OnUnload
Zajímavé je, že paměť alokovaná pro pole je vždy větší, než délka zjištěná prostým vynásobením počtu prvků pole s počtem bajtů pro jeden prvek. V céčku totiž skutečně platí:
alokovaná_paměť = délka_pole * sizeof(typ)
V Javě je však situace poněkud komplikovanější, protože pole jsou chápána jako objekty a tudíž se při jejich ukládání na haldu kromě vlastních prvků ukládají i další informace. Především se jedná o hlavičku, jejíž délka je většinou rovna osmi bajtům. Hlavička je používána pro identifikaci objektu, využívá ji správce paměti atd. atd. U 64bitových virtuálních strojů a velkých kapacit haldy může být hlavička i větší, setkat se lze i s délkou 16 bajtů! Taktéž je nutné uložit délku pole, což je (prozatím) čtyřbajtová hodnota (délka pole je jeho nedílnou součástí, na rozdíl od céčka). Navíc se podle architektury, na níž JVM běží, na konci pole nachází výplň (padding) tak velká, aby celková velikost objektu byla dělitelná 8 či 16 bajty. To je i důvod, proč například jednoprvkové pole boolean[1] ve skutečnosti na haldě obsadí šestnáct bajtů.
7. Řetězce na haldě – aneb specifický typ objektů
Dnes se ještě budeme zabývat popisem způsobu uložení řetězců na haldě, získáním těchto řetězců a jejich (i když poněkud omezeným) výpisem. Připomeňme si, co se vlastně v Javě pod pojmem „řetězec“ (string) skrývá. Na jednu stranu se jedná o pouhou třídu, jejíž instance lze vytvářet s využitím operátoru new, na stranu druhou však pro práci s řetězci existuje podpora v samotné syntaxi a sémantice tohoto programovacího jazyka, protože do programů je možné zapisovat řetězcové literály (konstantní řetězce) a navíc pro řetězce existují přetížené verze operátorů + a += (nikoli však již operátoru ==, což stále vede ke vzniku mnohdy těžko objevitelných sémantických chyb). To však není vše, protože řetězcové literály jsou ukládány v constant poolu (http://www.root.cz/clanky/pohled-pod-kapotu-jvm-2-cast-podrobnejsi-analyza-obsahu-constant-poolu), takže řetězce jsou skutečně na platformě Java zpracovávány jiným způsobem, než další třídy a jejich instance (navíc s JDK7 existuje pro řetězce speciální forma rozeskoku switch – viz též úvodní článek tohoto seriálu http://www.root.cz/clanky/novinky-v-nbsp-jdk-7-aneb-mirny-pokrok-v-nbsp-mezich-zakona-1/).
Z hlediska vývojáře je důležitý taktéž fakt, že řetězce jsou neměnitelné (immutable), což s sebou přináší jak některé výhody, tak i komplikace, především ve chvíli, kdy se mají s řetězci provádět složitější operace. Interně řetězce vypadají poměrně jednoduše, alespoň při prvním pohledu, protože jsou tvořeny polem char[], což znamená, že každý prvek tohoto pole má velikost šestnáct bitů. Pro většinu abeced platí, že každý znak uložený v tomto poli skutečně odpovídá jednomu znaku dané abecedy (latinky, azbuky, alfabety …), ovšem ve skutečnosti není možné v rozsahu 0..216 reprezentovat libovolný znak definovaný v normě Unicode. To platí pouze pro znaky s kódem 0×0000 až 0×FFFF, které se někdy označují názvem Basic Multilingual Plane (BMP), které jsou v řetězci uloženy stylem jeden_znak_abecedy == 1 char. Pro znaky s vyšším kódem (až 0×10FFFF) je nutné v řetězci vyhradit pár hodnot typu char, tento pár se jmenuje surrogate pair.
8. Struktura objektu představujícího řetězec
V předchozích kapitolách jsme si řekli, že pole jsou na haldě uloženy takovým způsobem, že je pro ně nutné alokovat větší objem paměti, než by to odpovídalo počtu prvků pole vynásobeného velikostí jednoho prvku. V případě řetězců je poněkud paradoxně situace ještě horší, protože řetězec je na haldě představován dvěma objekty – instancí třídy String a instancí pole char[] – toto pole je sice atributem třídy String, ovšem pole jsou, jak již víme, chápána jako objekty, tudíž se namísto pole ukládá pouze jeho reference. To s sebou nese určitou (osobně bych řekl, že velmi velkou) režii, která vede k tomu, že řetězce používané v typických javovských aplikacích spotřebují daleko větší množství operační paměti, než je na první pohled patrné.
Nejprve se podívejme na to, jak je uložena každá instance třídy String:
# | 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ů |
Celková velikost paměti pro uložení instance třídy String je tedy minimálně 24 bajtů, a to pouze v případě, že reference na objekty má šířku 32 bitů a nikoli 64 bitů (i to je možné v případě větších kapacit hald). Oněch 24 bajtů se označuje shallow size („mělká velikost“ značící, že nebereme v úvahu velikosti objektových atributů) a je stejná pro všechny instance třídy String.
To však samozřejmě není vše, protože instance třídy String se pouze odkazuje na pole char[], které je na haldě uloženo následovně:
# | 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 | x | length*2 | vlastní obsah řetězce |
4 | 0 nebo 2 | PADDING | výplň, aby celková délka objektu byla dělitelná čtyřmi (někdy osmi) |
I pro pole char[], které bude obsahovat pouze jeden znak, tedy bude na haldě alokována paměť o velikosti šestnácti bajtů!
9. Efektivita uložení řetězců na haldě
Z textu uvedeného v předchozí kapitole plyne, že uložení především kratších řetězců nemusí být vůbec efektivní z hlediska využití operační paměti. Pro úplnost si řekněme, že celková kapacita paměti alokovaná na haldě pro určitý objekt se nazývá retained size a v případě řetězců se jedná o součet shallow size s velikostí paměti alokované pro pole char[], tedy:
Retained size = Shallow size + char[].HEADER + char[].length + (chars * 2 + padding)
Když si uvědomíme, že poměr mezi pamětí vyhrazenou POUZE pro vlastní obsah řetězce (znaky zde uložené) a hodnotou retained size nelineárně roste se zvětšující se délkou řetězce, vyjde nám, že zejména při práci s krátkými řetězci je halda využívána dosti neefektivním způsobem (jak neefektivním, si ukážeme níže). Krátké řetězce se však v Javovských aplikacích vyskytují velmi často; používají se jako klíče do hešovacích map, jména souborů bývají krátká, jména a příjmení lidí taktéž, nehledě na jména tříd a balíčků (řetězce využívané interně samotnou JVM)… Navíc se k tomu přidává fakt, že například při používání aplikací v anglicky mluvících prostředích (ale i v dalších zemích se základní či rozšířenou latinkou) se prakticky vždy využije pouze 8 bitů z 16 bitů alokovaných na každou hodnotu typu char, takže je využití ve skutečnosti ještě poloviční oproti prostému poměru chars*2/retained_size.
Mimochodem – i při spuštění toho nejjednoduššího javovského programu typu Hello world se na haldě vytvoří přes 900 převážně krátkých řetězců.
10. Výpočet efektivity uložení řetězců na haldě
Abychom si ukázali, že uložení krátkých řetězců na haldě je skutečně v mnoha případech dosti neefektivní, můžeme si vyzkoušet vygenerovat tabulku a z ní odvozený graf, na němž bude vykreslen poměr chars*2/retained_size, tj. poměr mezi počtem znaků uložených v řetězci (dvojka je zde z toho důvodu, že šířka datového typu char je v Javě 16 bitů). Tabulka bude vygenerována pro všechny délky řetězce, pro něž je efektivita uložení menší než 90% (znovu je důležité zopakovat, že v případě programů pracujících pouze s ASCII sadou je toto číslo ve skutečnosti vlastně poloviční). Program pro výpočet této tabulky je jednoduchý (zdrojový kód si můžete stáhnout z adresy http://icedtea.classpath.org/people/ptisnovs/jvm-tools/file/f61c1eead131/tools/string_storage.c):
/* Vypocet efektivity ulozeni retezcu na halde. */ /* Autor: Pavel Tisnovsky */ #include <stdio.h> /* Hlavicka objektu ulozenych na halde */ const int HEADER_SIZE = 8; /* Atributy kazde instance tridy String */ const int STRING_CLASS_OFFSET_FIELD = 4; const int STRING_CLASS_COUNT_FIELD = 4; const int STRING_CLASS_HASH_FIELD = 4; const int STRING_CLASS_ARRAY_REF = 4; /* Atribut ulozeny spolecne s kazdym polem. */ const int ARRAY_LENGTH = 4; int main(int argc, char **argv) { int str_length = 0; float effectivity; /* Hlavicka tabulky */ printf("j_chars\tBytes\tPadding\tBytes+Padding\tShallow size\tRetained size\tEffectivity\n"); /* Vypocet a zobrazeni efektivity ulozeni retezcu pro ruzne delky */ do { /* char v Jave ma delku 2 bajty */ int bytes = str_length << 1; /* dorovnani delky na velikost delitelnou ctyrmi */ int padding = (str_length % 2) << 1; /* na zacatku je vetsinou 24 bajtu "rozsirene hlavicky" */ int shallow_size = HEADER_SIZE + STRING_CLASS_OFFSET_FIELD + STRING_CLASS_COUNT_FIELD + STRING_CLASS_HASH_FIELD + STRING_CLASS_ARRAY_REF; int retained_size = shallow_size + HEADER_SIZE + ARRAY_LENGTH + bytes + padding; /* 0..100 % */ effectivity = 100.0 * bytes / retained_size; /* tisk jednoho radku tabulky */ printf("%d\t%d\t%d\t%d\t%d\t%d\t%5.1f%%\n", str_length, bytes, padding, bytes + padding, shallow_size, retained_size, effectivity); str_length++; } while (effectivity < 90.0); return 0; }
Nejprve bude ukázán numerický výpis:
j_chars | Bytes | Padding | Bytes+Padding | Shallow size | Retained size | Effectivity |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 24 | 36 | 0.0% |
1 | 2 | 2 | 4 | 24 | 40 | 5.0% |
2 | 4 | 0 | 4 | 24 | 40 | 10.0% |
3 | 6 | 2 | 8 | 24 | 44 | 13.6% |
4 | 8 | 0 | 8 | 24 | 44 | 18.2% |
5 | 10 | 2 | 12 | 24 | 48 | 20.8% |
6 | 12 | 0 | 12 | 24 | 48 | 25.0% |
7 | 14 | 2 | 16 | 24 | 52 | 26.9% |
8 | 16 | 0 | 16 | 24 | 52 | 30.8% |
9 | 18 | 2 | 20 | 24 | 56 | 32.1% |
10 | 20 | 0 | 20 | 24 | 56 | 35.7% |
11 | 22 | 2 | 24 | 24 | 60 | 36.7% |
12 | 24 | 0 | 24 | 24 | 60 | 40.0% |
13 | 26 | 2 | 28 | 24 | 64 | 40.6% |
14 | 28 | 0 | 28 | 24 | 64 | 43.8% |
15 | 30 | 2 | 32 | 24 | 68 | 44.1% |
16 | 32 | 0 | 32 | 24 | 68 | 47.1% |
17 | 34 | 2 | 36 | 24 | 72 | 47.2% |
18 | 36 | 0 | 36 | 24 | 72 | 50.0% |
19 | 38 | 2 | 40 | 24 | 76 | 50.0% |
20 | 40 | 0 | 40 | 24 | 76 | 52.6% |
21 | 42 | 2 | 44 | 24 | 80 | 52.5% |
22 | 44 | 0 | 44 | 24 | 80 | 55.0% |
23 | 46 | 2 | 48 | 24 | 84 | 54.8% |
24 | 48 | 0 | 48 | 24 | 84 | 57.1% |
25 | 50 | 2 | 52 | 24 | 88 | 56.8% |
26 | 52 | 0 | 52 | 24 | 88 | 59.1% |
27 | 54 | 2 | 56 | 24 | 92 | 58.7% |
28 | 56 | 0 | 56 | 24 | 92 | 60.9% |
29 | 58 | 2 | 60 | 24 | 96 | 60.4% |
30 | 60 | 0 | 60 | 24 | 96 | 62.5% |
31 | 62 | 2 | 64 | 24 | 100 | 62.0% |
32 | 64 | 0 | 64 | 24 | 100 | 64.0% |
33 | 66 | 2 | 68 | 24 | 104 | 63.5% |
34 | 68 | 0 | 68 | 24 | 104 | 65.4% |
35 | 70 | 2 | 72 | 24 | 108 | 64.8% |
36 | 72 | 0 | 72 | 24 | 108 | 66.7% |
37 | 74 | 2 | 76 | 24 | 112 | 66.1% |
38 | 76 | 0 | 76 | 24 | 112 | 67.9% |
39 | 78 | 2 | 80 | 24 | 116 | 67.2% |
40 | 80 | 0 | 80 | 24 | 116 | 69.0% |
41 | 82 | 2 | 84 | 24 | 120 | 68.3% |
42 | 84 | 0 | 84 | 24 | 120 | 70.0% |
43 | 86 | 2 | 88 | 24 | 124 | 69.4% |
44 | 88 | 0 | 88 | 24 | 124 | 71.0% |
45 | 90 | 2 | 92 | 24 | 128 | 70.3% |
46 | 92 | 0 | 92 | 24 | 128 | 71.9% |
47 | 94 | 2 | 96 | 24 | 132 | 71.2% |
48 | 96 | 0 | 96 | 24 | 132 | 72.7% |
49 | 98 | 2 | 100 | 24 | 136 | 72.1% |
50 | 100 | 0 | 100 | 24 | 136 | 73.5% |
51 | 102 | 2 | 104 | 24 | 140 | 72.9% |
52 | 104 | 0 | 104 | 24 | 140 | 74.3% |
53 | 106 | 2 | 108 | 24 | 144 | 73.6% |
54 | 108 | 0 | 108 | 24 | 144 | 75.0% |
55 | 110 | 2 | 112 | 24 | 148 | 74.3% |
56 | 112 | 0 | 112 | 24 | 148 | 75.7% |
57 | 114 | 2 | 116 | 24 | 152 | 75.0% |
58 | 116 | 0 | 116 | 24 | 152 | 76.3% |
59 | 118 | 2 | 120 | 24 | 156 | 75.6% |
60 | 120 | 0 | 120 | 24 | 156 | 76.9% |
61 | 122 | 2 | 124 | 24 | 160 | 76.3% |
62 | 124 | 0 | 124 | 24 | 160 | 77.5% |
63 | 126 | 2 | 128 | 24 | 164 | 76.8% |
64 | 128 | 0 | 128 | 24 | 164 | 78.0% |
65 | 130 | 2 | 132 | 24 | 168 | 77.4% |
66 | 132 | 0 | 132 | 24 | 168 | 78.6% |
67 | 134 | 2 | 136 | 24 | 172 | 77.9% |
68 | 136 | 0 | 136 | 24 | 172 | 79.1% |
69 | 138 | 2 | 140 | 24 | 176 | 78.4% |
70 | 140 | 0 | 140 | 24 | 176 | 79.5% |
71 | 142 | 2 | 144 | 24 | 180 | 78.9% |
72 | 144 | 0 | 144 | 24 | 180 | 80.0% |
73 | 146 | 2 | 148 | 24 | 184 | 79.3% |
74 | 148 | 0 | 148 | 24 | 184 | 80.4% |
75 | 150 | 2 | 152 | 24 | 188 | 79.8% |
76 | 152 | 0 | 152 | 24 | 188 | 80.9% |
77 | 154 | 2 | 156 | 24 | 192 | 80.2% |
78 | 156 | 0 | 156 | 24 | 192 | 81.3% |
79 | 158 | 2 | 160 | 24 | 196 | 80.6% |
80 | 160 | 0 | 160 | 24 | 196 | 81.6% |
81 | 162 | 2 | 164 | 24 | 200 | 81.0% |
82 | 164 | 0 | 164 | 24 | 200 | 82.0% |
83 | 166 | 2 | 168 | 24 | 204 | 81.4% |
84 | 168 | 0 | 168 | 24 | 204 | 82.4% |
85 | 170 | 2 | 172 | 24 | 208 | 81.7% |
86 | 172 | 0 | 172 | 24 | 208 | 82.7% |
87 | 174 | 2 | 176 | 24 | 212 | 82.1% |
88 | 176 | 0 | 176 | 24 | 212 | 83.0% |
89 | 178 | 2 | 180 | 24 | 216 | 82.4% |
90 | 180 | 0 | 180 | 24 | 216 | 83.3% |
91 | 182 | 2 | 184 | 24 | 220 | 82.7% |
92 | 184 | 0 | 184 | 24 | 220 | 83.6% |
93 | 186 | 2 | 188 | 24 | 224 | 83.0% |
94 | 188 | 0 | 188 | 24 | 224 | 83.9% |
95 | 190 | 2 | 192 | 24 | 228 | 83.3% |
96 | 192 | 0 | 192 | 24 | 228 | 84.2% |
97 | 194 | 2 | 196 | 24 | 232 | 83.6% |
98 | 196 | 0 | 196 | 24 | 232 | 84.5% |
99 | 198 | 2 | 200 | 24 | 236 | 83.9% |
100 | 200 | 0 | 200 | 24 | 236 | 84.7% |
101 | 202 | 2 | 204 | 24 | 240 | 84.2% |
102 | 204 | 0 | 204 | 24 | 240 | 85.0% |
103 | 206 | 2 | 208 | 24 | 244 | 84.4% |
104 | 208 | 0 | 208 | 24 | 244 | 85.2% |
105 | 210 | 2 | 212 | 24 | 248 | 84.7% |
106 | 212 | 0 | 212 | 24 | 248 | 85.5% |
107 | 214 | 2 | 216 | 24 | 252 | 84.9% |
108 | 216 | 0 | 216 | 24 | 252 | 85.7% |
109 | 218 | 2 | 220 | 24 | 256 | 85.2% |
110 | 220 | 0 | 220 | 24 | 256 | 85.9% |
111 | 222 | 2 | 224 | 24 | 260 | 85.4% |
112 | 224 | 0 | 224 | 24 | 260 | 86.2% |
113 | 226 | 2 | 228 | 24 | 264 | 85.6% |
114 | 228 | 0 | 228 | 24 | 264 | 86.4% |
115 | 230 | 2 | 232 | 24 | 268 | 85.8% |
116 | 232 | 0 | 232 | 24 | 268 | 86.6% |
117 | 234 | 2 | 236 | 24 | 272 | 86.0% |
118 | 236 | 0 | 236 | 24 | 272 | 86.8% |
119 | 238 | 2 | 240 | 24 | 276 | 86.2% |
120 | 240 | 0 | 240 | 24 | 276 | 87.0% |
121 | 242 | 2 | 244 | 24 | 280 | 86.4% |
122 | 244 | 0 | 244 | 24 | 280 | 87.1% |
123 | 246 | 2 | 248 | 24 | 284 | 86.6% |
124 | 248 | 0 | 248 | 24 | 284 | 87.3% |
125 | 250 | 2 | 252 | 24 | 288 | 86.8% |
126 | 252 | 0 | 252 | 24 | 288 | 87.5% |
127 | 254 | 2 | 256 | 24 | 292 | 87.0% |
128 | 256 | 0 | 256 | 24 | 292 | 87.7% |
129 | 258 | 2 | 260 | 24 | 296 | 87.2% |
130 | 260 | 0 | 260 | 24 | 296 | 87.8% |
131 | 262 | 2 | 264 | 24 | 300 | 87.3% |
132 | 264 | 0 | 264 | 24 | 300 | 88.0% |
133 | 266 | 2 | 268 | 24 | 304 | 87.5% |
134 | 268 | 0 | 268 | 24 | 304 | 88.2% |
135 | 270 | 2 | 272 | 24 | 308 | 87.7% |
136 | 272 | 0 | 272 | 24 | 308 | 88.3% |
137 | 274 | 2 | 276 | 24 | 312 | 87.8% |
138 | 276 | 0 | 276 | 24 | 312 | 88.5% |
139 | 278 | 2 | 280 | 24 | 316 | 88.0% |
140 | 280 | 0 | 280 | 24 | 316 | 88.6% |
141 | 282 | 2 | 284 | 24 | 320 | 88.1% |
142 | 284 | 0 | 284 | 24 | 320 | 88.8% |
143 | 286 | 2 | 288 | 24 | 324 | 88.3% |
144 | 288 | 0 | 288 | 24 | 324 | 88.9% |
145 | 290 | 2 | 292 | 24 | 328 | 88.4% |
146 | 292 | 0 | 292 | 24 | 328 | 89.0% |
147 | 294 | 2 | 296 | 24 | 332 | 88.6% |
148 | 296 | 0 | 296 | 24 | 332 | 89.2% |
149 | 298 | 2 | 300 | 24 | 336 | 88.7% |
150 | 300 | 0 | 300 | 24 | 336 | 89.3% |
151 | 302 | 2 | 304 | 24 | 340 | 88.8% |
152 | 304 | 0 | 304 | 24 | 340 | 89.4% |
153 | 306 | 2 | 308 | 24 | 344 | 89.0% |
154 | 308 | 0 | 308 | 24 | 344 | 89.5% |
155 | 310 | 2 | 312 | 24 | 348 | 89.1% |
156 | 312 | 0 | 312 | 24 | 348 | 89.7% |
157 | 314 | 2 | 316 | 24 | 352 | 89.2% |
158 | 316 | 0 | 316 | 24 | 352 | 89.8% |
159 | 318 | 2 | 320 | 24 | 356 | 89.3% |
160 | 320 | 0 | 320 | 24 | 356 | 89.9% |
161 | 322 | 2 | 324 | 24 | 360 | 89.4% |
162 | 324 | 0 | 324 | 24 | 360 | 90.0% |
Zajímavější je však graf, který ukazuje (pravá Y-ová osa a průběh vykreslený hnědou barvou), jak efektivita uložení nelineárně roste a teprve pro řetězce o délce 161 znaků překračuje 90 % (z 360 alokovaných bajtů se 324 bajtů použije pro uložení znaků, zbytek je vlastně režie).
![string_storage_graph](https://i.iinfo.cz/images/376/java-string-storage-efficiency.png)
11. Zdrojové kódy všech tří agentů a k nim příslušných testovacích příkladů
Podobně jako ve čtyřech předcházejících částech tohoto seriálu byly i dnešní tři 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.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:
12. 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