Meranie veľkostí objektov v JVM

1. Prehľad

V tomto výučbe sa dozvieme, koľko miesta každý objekt zaberá v halde Java.

Najskôr sa oboznámime s rôznymi metrikami na výpočet veľkostí objektov. Potom si ukážeme niekoľko spôsobov, ako merať veľkosti inštancií.

Rozloženie pamäte runtime dátových oblastí zvyčajne nie je súčasťou špecifikácie JVM a je ponechané na uváženie implementátora. Preto môže mať každá implementácia JVM inú stratégiu ako rozloženie objektov a polí v pamäti. To zase ovplyvní veľkosti inštancie za behu.

V tomto tutoriáli sa zameriavame na jednu konkrétnu implementáciu JVM: HotSpot JVM.

V celom návode tiež používame výrazy JVM a HotSpot JVM zameniteľné.

2. Plytké, zachované a hlboké veľkosti objektov

Na analýzu veľkostí objektov môžeme použiť tri rôzne metriky: plytké, zachované a hlboké veľkosti.

Pri výpočte malej veľkosti objektu berieme do úvahy iba samotný objekt. To znamená, že ak má objekt odkazy na iné objekty, vezmeme do úvahy iba veľkosť odkazu na cieľové objekty, nie ich skutočnú veľkosť. Napríklad:

Ako je uvedené vyššie, malá veľkosť Triple inštancia je iba súčtom troch referencií. Vylučujeme skutočnú veľkosť odkazovaných objektov, a to A1, B1, a C1, z tejto velkosti.

Naopak, hlboká veľkosť objektu okrem malej veľkosti zahŕňa aj veľkosť všetkých odkazovaných objektov:

Tu hlboká veľkosť Triple inštancia obsahuje tri referencie a skutočnú veľkosť súboru A1, B1, a C1. Preto majú hlboké veľkosti rekurzívnu povahu.

Keď GC získa späť pamäť obsadenú objektom, uvoľní konkrétne množstvo pamäte. Táto suma predstavuje zadržanú veľkosť tohto objektu:

Zadržaná veľkosť Triple inštancia obsahuje iba A1 a C1 navyše k Triple samotná inštancia. Na druhej strane táto ponechaná veľkosť nezahŕňa B1, od Pár inštancia má tiež odkaz na B1.

Niekedy tieto ďalšie odkazy sprostredkuje nepriamo samotný JVM. Preto môže byť výpočet zadržanej veľkosti komplikovanou úlohou.

Aby sme lepšie pochopili zachovanú veľkosť, mali by sme uvažovať v zmysle zberu odpadu. Zbieranie Triple inštancia robí A1 a C1 nedosiahnuteľný, ale B1 je stále dosiahnuteľné cez iný objekt. V závislosti od situácie môže byť zachovaná veľkosť kdekoľvek medzi plytkou a hlbokou veľkosťou.

3. Závislosť

Na kontrolu rozloženia pamäte objektov alebo polí v JVM použijeme nástroj Java Object Layout (JOL). Preto budeme musieť pridať veselé jadro závislosť:

 org.openjdk.jol jol-core 0,10 

4. Jednoduché typy údajov

Aby sme lepšie pochopili veľkosť zložitejších objektov, mali by sme najskôr vedieť, koľko miesta každý jednoduchý dátový typ zaberá. Za týmto účelom môžeme požiadať Java Memory Layout alebo JOL o vytlačenie informácií o VM:

System.out.println (VM.current (). Details ());

Vyššie uvedený kód vytlačí jednoduché veľkosti dátových typov nasledovne:

# Spustenie 64-bitového HotSpot VM. # Používanie komprimovaného OOP s 3-bitovým posunom. # Používanie komprimovaného klassu s 3-bitovým posunom. # Objekty sú zarovnané na 8 bajtov. # Veľkosti polí podľa typu: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bajty] # Veľkosti prvkov poľa: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bajty ]

Takže tu sú požiadavky na priestor pre každý jednoduchý dátový typ v JVM:

  • Odkazy na objekty zaberajú 4 bajty
  • boolovský a bajt hodnoty spotrebujú 1 bajt
  • krátky a char hodnoty spotrebujú 2 bajty
  • int a plavák hodnoty spotrebujú 4 bajty
  • dlho a dvojitý hodnoty spotrebujú 8 bajtov

To platí pre 32-bitové architektúry a tiež 64-bitové architektúry s účinnými komprimovanými odkazmi.

Za zmienku stojí tiež to, že všetky typy údajov spotrebúvajú rovnaké množstvo pamäte, ak sa používajú ako typy komponentov poľa.

4.1. Nekomprimované referencie

Ak deaktivujeme komprimované odkazy pomocou -XX: -UseCompressedOops ladiaci príznak, potom sa zmenia požiadavky na veľkosť:

# Objekty sú zarovnané na 8 bajtov. # Veľkosti polí podľa typu: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bajty] # Veľkosti prvkov poľa: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bajty ]

Teraz budú odkazy na objekty spotrebovať 8 bajtov namiesto 4 bajtov. Zvyšné dátové typy stále zaberajú rovnaké množstvo pamäte.

HotSpot JVM navyše nemôže používať komprimované odkazy, ak je veľkosť haldy viac ako 32 GB (pokiaľ nezmeníme zarovnanie objektu).

Záverom je, že ak explicitne deaktivujeme komprimované odkazy alebo ak je veľkosť haldy viac ako 32 GB, odkazy na objekty budú spotrebovávať 8 bajtov.

Teraz, keď poznáme spotrebu pamäte pre základné dátové typy, vypočítajme ju pre zložitejšie objekty.

5. Zložité objekty

Pri výpočte veľkosti zložitých objektov uvažujme vzťah typického profesora k kurzu:

public class Course {private String name; // konštruktor}

Každý Profesor, okrem osobných údajov môže mať aj zoznam Samozrejmes:

public class Professor {private String name; súkromný booleovský držiteľ; private List courses = new ArrayList (); súkromná int; private LocalDate birthDay; súkromné ​​zdvojnásobenieHodnotenie; // konštruktor}

5.1. Plytká veľkosť: Samozrejme Trieda

Malá veľkosť Samozrejme inštancie triedy by mali obsahovať 4-bajtový odkaz na objekt (pre názov pole) plus nejaké režijné náklady na objekt. Tento predpoklad môžeme skontrolovať pomocou JOL:

System.out.println (ClassLayout.parseClass (Course.class) .toPrintable ());

Týmto sa vytlačí nasledovné:

Interné objekty kurzu: TYP VEĽKOSTI POSUNU POPIS HODNOTA 0 12 (hlavička objektu) N / A 12 4 java.lang.String Názov kurzu N / A Veľkosť inštancie: 16 bajtov Straty priestoru: 0 bajtov interné + 0 bajtov externé = 0 bajtov spolu

Ako je uvedené vyššie, plytká veľkosť je 16 bajtov, vrátane odkazu na objekt 4 bajty názov pole plus hlavička objektu.

5.2. Plytká veľkosť: Profesor Trieda

Ak spustíme rovnaký kód pre Profesor trieda:

System.out.println (ClassLayout.parseClass (Professor.class) .toPrintable ());

Potom JOL vytlačí spotrebu pamäte pre Profesor trieda ako táto:

Interné objekty profesora: TYP VEĽKOSTI OFSETU POPIS HODNOTA 0 12 (hlavička objektu) N / A 12 4 int Professor.úroveň N / A 16 8 dvojité Professor.lastEvaluation N / A 24 1 boolean Professor.tenured N / A 25 3 (zarovnanie / medzera medzi polstrovaním) 28 4 java.lang.String Profesor.názov N / A 32 4 java.util.List Profesor.kurzov N / A 36 4 java.time.LocalDate Professor.birthDay N / A Veľkosť inštancie: 40 bajtov Straty priestoru: 3 bajty interné + 0 bajtov externé = spolu 3 bajty

Ako sme pravdepodobne očakávali, zapuzdrené polia spotrebúvajú 25 bajtov:

  • Tri odkazy na objekty, z ktorých každý spotrebuje 4 bajty. Celkovo teda 12 bajtov za odkazovanie na iné objekty
  • Jeden int ktorý spotrebuje 4 bajty
  • Jeden boolovský ktorý spotrebuje 1 bajt
  • Jeden dvojitý ktorá spotrebuje 8 bajtov

Pri pripočítaní réžie 12 bajtov k hlavičke objektu a 3 bajtov zarovnávacej výplne je plytká veľkosť 40 bajtov.

Kľúčovým údajom je, že okrem zapuzdreného stavu každého objektu by sme pri výpočte rôznych veľkostí objektov mali brať do úvahy hlavičku objektu a odsadenie zarovnania.

5.3. Plytká veľkosť: inštancia

The veľkosť() metóda v JOL poskytuje oveľa jednoduchší spôsob výpočtu malej veľkosti inštancie objektu. Ak spustíme nasledujúci úryvok:

Reťazec ds = "Dátové štruktúry"; Kurz kurzu = nový kurz (ds); System.out.println ("Plytká veľkosť je:" + VM.current (). SizeOf (kurz));

Vytlačí plytkú veľkosť nasledovne:

Plytká veľkosť je: 16

5.4. Nekomprimovaná veľkosť

Ak deaktivujeme komprimované odkazy alebo použijeme viac ako 32 GB haldy, plytká veľkosť sa zvýši:

Interné objekty profesora: TYP VEĽKOSTI OFSETU POPIS HODNOTA 0 16 (hlavička objektu) N / A 16 8 dvojité Professor.lastEvaluation N / A 24 4 int Professor.level N / A 28 1 boolean Professor.tenured N / A 29 3 (zarovnanie / medzera medzi polstrovaním) 32 8 java.lang.String Profesor.názov N / A 40 8 java.util.List Profesor.kurzov N / A 48 8 java.time.LocalDate Professor.birthDay N / A Veľkosť inštancie: 56 bajtov Straty priestoru: 3 bajty interné + 0 bajtov externé = spolu 3 bajty

Keď sú komprimované odkazy zakázané, hlavička objektu a odkazy na objekty spotrebujú viac pamäte. Preto, ako je uvedené vyššie, teraz to isté Profesor trieda spotrebuje o 16 bajtov viac.

5.5. Hlboká veľkosť

Na výpočet hlbokej veľkosti by sme mali zahrnúť celú veľkosť samotného objektu a všetkých jeho spolupracovníkov. Napríklad pre tento jednoduchý scenár:

Reťazec ds = "Dátové štruktúry"; Kurz kurzu = nový kurz (ds);

Hlboká veľkosť Samozrejme inštancia sa rovná malej veľkosti súboru Samozrejme samotná inštancia plus veľká veľkosť tohto konkrétneho String inštancia.

S tým, čo bolo povedané, pozrime sa, koľko priestoru to je String inštancia spotrebuje:

System.out.println (ClassLayout.parseInstance (ds) .toPrintable ());

Každý String inštancia zapuzdruje a char [] (viac o tom neskôr) a an int hashcode:

java.lang.Stringové interné objekty objektu: TYP VEĽKOSTI POPISU HODNOTA 0 4 (hlavička objektu) 01 00 00 00 4 4 (hlavička objektu) 00 00 00 00 8 4 (hlavička objektu) da 02 00 f8 12 4 char [] Reťazec. hodnota [D, a, t, a,, S, t, r, u, c, t, u, r, e, s] 16 4 int String.hash 0 20 4 (strata v dôsledku ďalšieho zarovnania objektu) Instancia veľkosť: 24 bajtov Straty priestoru: 0 bajtov interné + 4 bajty vonkajšie = 4 bajty spolu

Toto je plytká veľkosť String inštancia je 24 bajtov, ktoré zahŕňajú 4 bajty kódu hash uloženého v pamäti a 4 bajty char [] referenčné a ďalšie typické režijné náklady na objekt.

Ak chcete zistiť skutočnú veľkosť súboru char [], môžeme analyzovať aj jej rozloženie triedy:

System.out.println (ClassLayout.parseInstance (ds.toCharArray ()). ToPrintable ());

Usporiadanie char [] vyzerá takto:

[Vnútorné objekty C: TYP VEĽKOSTI ODSADENIA POPIS HODNOTA 0 4 (hlavička objektu) 01 00 00 00 4 4 (hlavička objektu) 00 00 00 00 8 4 (hlavička objektu) 41 00 00 f8 12 4 (hlavička objektu) 0f 00 00 00 16 30 znakov [C. N / A 46 2 (strata v dôsledku nasledujúceho zarovnania objektu) Veľkosť inštancie: 48 bajtov Straty priestoru: 0 bajtov interné + 2 bajty externé = 2 bajty spolu

Takže máme 16 bajtov pre Samozrejme napríklad 24 bajtov pre server String inštancia a nakoniec 48 bajtov pre server char []. Celkovo je to hlboká veľkosť Samozrejme inštancia je 88 bajtov.

So zavedením kompaktných reťazcov v prostredí Java 9 začal String trieda interne používa a byte [] na uloženie znakov:

java.lang.Stringové interné objekty objektu: TYP VEĽKOSTI POPISU 0 4 (hlavička objektu) 4 4 (hlavička objektu) 8 4 (hlavička objektu) 12 4 byte [] String.value # pole bajtov 16 4 int String.hash 20 1 byte String.coder # encodig 21 3 (strata v dôsledku ďalšieho zarovnania objektu)

Preto na Java 9+ celková stopa Samozrejme inštancia bude 72 bajtov namiesto 88 bajtov.

5.6. Rozloženie grafu objektu

Namiesto samostatnej analýzy rozloženia triedy každého objektu v grafe objektov môžeme použiť znak GraphLayout. S GraphLayot, proste prejdeme začiatočným bodom grafu objektov a bude z tohto východiskového bodu hlásiť rozloženie všetkých dostupných objektov. Týmto spôsobom môžeme vypočítať hlbokú veľkosť počiatočného bodu grafu.

Napríklad môžeme vidieť celkovú stopu súboru Samozrejme napríklad takto:

System.out.println (GraphLayout.parseInstance (kurz) .toFootprint ());

Z ktorého sa vytlačí nasledujúci súhrn:

[chránený e-mailom] stopa: POČET AVG SUMA POPIS 1 48 48 [C 1 16 16 com.baeldung.objectsize.Course 1 24 24 java.lang.String 3 88 (celkom)

To je celkom 88 bajtov. The Celková velkosť() metóda vráti celkovú stopu objektu, ktorá je 88 bajtov:

System.out.println (GraphLayout.parseInstance (kurz). TotalSize ());

6. Prístrojové vybavenie

Na výpočet malej veľkosti objektu môžeme tiež použiť inštrumentálny balík Java a agentov Java. Najskôr by sme mali vytvoriť triedu s a premain () metóda:

public class ObjectSizeCalculator {private static Instrumentation instrumentation; public static void premain (String args, Instrumentation inst) {instrumentation = inst; } public static long sizeOf (Object o) {return instrumentation.getObjectSize (o); }}

Ako je uvedené vyššie, použijeme getObjectSize () metóda na zistenie malej veľkosti objektu. Potrebujeme tiež súbor manifestu:

Trieda premain: com.baeldung.objectsize.ObjectSizeCalculator

Potom pomocou tohto MANIFEST.MF súbor, môžeme vytvoriť súbor JAR a použiť ho ako agenta Java:

$ jar cmf MANIFEST.MF agent.jar * .class

Nakoniec, ak spustíme akýkoľvek kód s -javaagent: /path/to/agent.jar argument, potom môžeme použiť veľkosť() metóda:

Reťazec ds = "Dátové štruktúry"; Kurz kurzu = nový kurz (ds); System.out.println (ObjectSizeCalculator.sizeOf (kurz));

Toto vytlačí 16 ako malú veľkosť súboru Samozrejme inštancia.

7. Štatistika triedy

Aby sme videli malú veľkosť objektov v už spustenej aplikácii, môžeme sa pozrieť na štatistiku triedy pomocou jcmd:

$ jcmd GC.class_stats [output_columns]

Napríklad môžeme vidieť veľkosť každej inštancie a počet všetkých Samozrejme prípady:

$ jcmd 63984 GC.class_stats InstSize, InstCount, InstBytes | kurz grep 63984: InstSize InstCount InstBytes ClassName 16 1 16 com.baeldung.objectsize.Course

Aj v tomto prípade ide o hlásenie o malej veľkosti každého z nich Samozrejme napríklad ako 16 bajtov.

Ak chcete zobraziť štatistiku triedy, mali by sme aplikáciu spustiť s -XX: + UnlockDiagnosticVMOptions ladiaca vlajka.

8. Hald Dump

Ďalšou možnosťou na kontrolu veľkostí inštancií v spustených aplikáciách je použitie výpisov haldy. Týmto spôsobom môžeme vidieť zachovanú veľkosť pre každú inštanciu. Na odloženie haldy môžeme použiť jcmd takto:

$ jcmd GC.heap_dump [možnosti] / cesta / k / výpisu / súboru

Napríklad:

$ jcmd 63984 GC.heap_dump - všetky ~ / dump.hpro

Takto sa na danom mieste vytvorí výpis haldy. Tiež s -všetky Táto možnosť bude obsahovať všetky dosiahnuteľné a nedosiahnuteľné objekty na halde. Bez tejto možnosti vykoná JVM pred vytvorením výpisu haldy úplnú GC.

Po získaní výpisu haldy ho môžeme importovať do nástrojov, ako je Visual VM:

Ako je uvedené vyššie, zachovaná veľkosť jediného Samozrejme inštancia je 24 bajtov. Ako už bolo spomenuté, zachovaná veľkosť môže byť kdekoľvek medzi plytkou (16 bajtov) a hlbokou veľkosťou (88 bajtov).

Za zmienku tiež stojí, že Visual VM bol súčasťou distribúcií Oracle a Open JDK pred Java 9. Avšak už to neplatí ako pre Java 9 a mali by sme si Visual VM stiahnuť z jeho webových stránok osobitne.

9. Záver

V tomto tutoriáli sme sa oboznámili s rôznymi metrikami na meranie veľkostí objektov za behu JVM. Potom sme skutočne zmerali veľkosť inštancie pomocou rôznych nástrojov, ako sú JOL, Java Agents a jcmd obslužný program príkazového riadku.

Ako obvykle sú všetky príklady dostupné na GitHub.