Måling af objektstørrelser i JVM

1. Oversigt

I denne vejledning skal vi se, hvor meget plads hvert objekt bruger i Java-bunken.

Først bliver vi fortrolige med forskellige metrics til beregning af objektstørrelser. Derefter skal vi se et par måder at måle instansstørrelser på.

Normalt er hukommelseslayoutet for runtime-dataområder ikke en del af JVM-specifikationen og overlades til implementeringsmyndighedens skøn. Derfor kan hver JVM-implementering have en anden strategi til layout af objekter og arrays i hukommelsen. Dette vil igen påvirke forekomststørrelserne ved kørsel.

I denne vejledning fokuserer vi på en specifik JVM-implementering: HotSpot JVM.

Vi bruger også JVM og HotSpot JVM-termerne om hverandre i hele tutorialen.

2. Størrelser på lave, fastholdte og dybe objekter

For at analysere objektstørrelserne kan vi bruge tre forskellige metrics: Shallow, bevarede og dybe størrelser.

Når vi beregner den objektive overflade, betragter vi kun selve objektet. Det vil sige, hvis objektet har referencer til andre objekter, betragter vi kun referencestørrelsen til målobjekterne, ikke deres faktiske objektstørrelse. For eksempel:

Som vist ovenfor er den lave overflade af Tredobbelt instans er kun en sum af tre referencer. Vi udelukker den faktiske størrelse af de henviste objekter, nemlig A1, B1, og C1, fra denne størrelse.

Tværtimod, den dybe størrelse af et objekt inkluderer størrelsen på alle henviste objekter ud over den lave størrelse:

Her er den dybe størrelse af Tredobbelt forekomst indeholder tre referencer plus den faktiske størrelse på A1, B1, og C1. Derfor er dybe størrelser rekursive i naturen.

Når GC genvinder hukommelsen optaget af et objekt, frigør den en bestemt mængde hukommelse. Beløbet er den bevarede størrelse af objektet:

Den bibeholdte størrelse af Tredobbelt eksempel inkluderer kun A1 og C1 ud over Tredobbelt instans selv. På den anden side inkluderer denne bevarede størrelse ikke B1, siden Par eksempel har også en henvisning til B1.

Nogle gange er disse ekstra referencer indirekte foretaget af JVM selv. Derfor kan beregning af den bevarede størrelse være en kompliceret opgave.

For bedre at forstå den bevarede størrelse skal vi tænke i forhold til affaldsindsamlingen. Indsamling af Tredobbelt eksempel gør A1 og C1 uopnåelig, men den B1 kan stadig nås via et andet objekt. Afhængigt af situationen kan den bevarede størrelse være hvor som helst mellem den lave og dybe størrelse.

3. Afhængighed

For at inspicere hukommelseslayoutet på objekter eller arrays i JVM skal vi bruge Java Object Layout (JOL) -værktøjet. Derfor bliver vi nødt til at tilføje jol-core afhængighed:

 org.openjdk.jol jol-core 0.10 

4. Enkle datatyper

For at få en bedre forståelse af størrelsen på mere komplekse objekter, skal vi først vide, hvor meget plads hver enkelt datatype bruger. For at gøre det kan vi bede Java Memory Layout eller JOL om at udskrive VM-oplysningerne:

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

Ovenstående kode udskriver de enkle datatypestørrelser som følger:

# Kører 64-bit HotSpot VM. # Brug af komprimeret oop med 3-bit shift. # Brug af komprimeret klass med 3-bit shift. # Objekter er 8 byte justeret. # Feltstørrelser efter type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] # Array-elementstørrelser: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes ]

Så her er pladskravene til hver enkelt datatype i JVM:

  • Objektreferencer bruger 4 byte
  • boolsk og byte værdier forbruger 1 byte
  • kort og char værdier forbruger 2 byte
  • int og flyde værdier forbruger 4 byte
  • lang og dobbelt værdier forbruger 8 byte

Dette gælder i 32-bit arkitekturer og også 64-bit arkitekturer med komprimerede referencer i kraft.

Det er også værd at nævne, at alle datatyper bruger den samme mængde hukommelse, når de bruges som array-komponenttyper.

4.1. Ukomprimerede referencer

Hvis vi deaktiverer de komprimerede referencer via -XX: -UseCompressedOops tuning flag, så ændres størrelseskravene:

# Objekter er 8 byte justeret. # Feltstørrelser efter type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes] # Array-elementstørrelser: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes ]

Nu bruger objektreferencer 8 byte i stedet for 4 byte. De resterende datatyper bruger stadig den samme mængde hukommelse.

Desuden kan HotSpot JVM heller ikke bruge de komprimerede referencer, når bunkestørrelsen er mere end 32 GB (medmindre vi ændrer objektjusteringen).

Den nederste linje er, hvis vi eksplicit deaktiverer de komprimerede referencer, eller bunkestørrelsen er mere end 32 GB, vil objektreferencerne forbruge 8 byte.

Nu hvor vi kender hukommelsesforbruget til grundlæggende datatyper, lad os beregne det for mere komplekse objekter.

5. Komplekse objekter

For at beregne størrelsen for komplekse objekter, lad os overveje en typisk professor til kursusforhold:

offentlig klasse Kursus {privat strengnavn; // konstruktør}

Hver Professor, ud over de personlige oplysninger, kan have en liste over Rutes:

offentlig klasse professor {privat strengnavn; privat boolsk fast ejendom; private List-kurser = ny ArrayList (); privat int niveau; privat LocalDate fødselsdag; privat dobbelt lastEvaluation; // konstruktør}

5.1. Lav størrelse: den Rute Klasse

Den lave overflade af Rute klasseinstanser skal omfatte en 4-byte-objektreference (for navn felt) plus noget objekt overhead. Vi kan kontrollere denne antagelse ved hjælp af JOL:

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

Dette vil udskrive følgende:

Internt for kursusobjekt: OFFSET STØRRELSE TYPE BESKRIVELSE VÆRDI 0 12 (objektoverskrift) Ikke relevant 12 4 java.lang.Streng Kurs.navn Ikke relevant Instansstørrelse: 16 bytes Rumtab: 0 bytes internt + 0 bytes eksternt = 0 bytes i alt

Som vist ovenfor er den lavvandede størrelse 16 byte, inklusive en 4 bytes objekthenvisning til navn felt plus objektoverskriften.

5.2. Lav størrelse: den Professor Klasse

Hvis vi kører den samme kode til Professor klasse:

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

Derefter udskriver JOL hukommelsesforbruget til Professor klasse som følgende:

Professor objekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE VÆRDI 0 12 (objekt header) N / A 12 4 int Professor. Niveau N / A 16 8 dobbelt Professor. SidsteEvaluering N / A 24 1 boolsk professor. Holdet N / A 25 3 (justering / polstringsgab) 28 4 java.lang.String Professor.name N / A 32 4 java.util.List Professor.courses N / A 36 4 java.time.LocalDate Professor.birthDay N / A Instansstørrelse: 40 bytes Rumtab: 3 bytes internt + 0 bytes eksternt = 3 bytes i alt

Som vi sandsynligvis forventede, forbruger de indkapslede felter 25 byte:

  • Tre objektreferencer, der hver bruger 4 byte. Så 12 bytes i alt til henvisning til andre objekter
  • En int der forbruger 4 byte
  • En boolsk der forbruger 1 byte
  • En dobbelt der forbruger 8 byte

Tilføjelse af de 12 bytes overhead af objektoverskriften plus 3 bytes af justeringspolstring, den lave størrelse er 40 bytes.

Den vigtigste afhentning her er, ud over den indkapslede tilstand for hvert objekt, skal vi overveje objektets overskrift og justeringspolstring ved beregning af forskellige objektstørrelser.

5.3. Lav størrelse: en instans

Det sizeOf () metode i JOL giver en meget enklere måde at beregne den lave størrelse på en objektinstans. Hvis vi kører følgende uddrag:

String ds = "Datastrukturer"; Kursuskursus = nyt kursus (ds); System.out.println ("Den lave størrelse er:" + VM.current (). SizeOf (kursus));

Den udskriver den lave størrelse som følger:

Den lave størrelse er: 16

5.4. Ukomprimeret størrelse

Hvis vi deaktiverer de komprimerede referencer eller bruger mere end 32 GB af bunken, øges den lave størrelse:

Professor objekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE VÆRDI 0 16 (objekt header) N / A 16 8 dobbelt Professor.lastEvaluation N / A 24 4 int Professor. Niveau N / A 28 1 boolsk professor. Holdet N / A 29 3 (justering / polstringsgab) 32 8 java.lang.String Professor.name N / A 40 8 java.util.List Professor.courses N / A 48 8 java.time.LocalDate Professor.birthDay N / A Instansstørrelse: 56 bytes Rumtab: 3 byte interne + 0 byte eksterne = 3 bytes i alt

Når de komprimerede referencer er deaktiveret, vil objektoverskriften og objektreferencerne forbruge mere hukommelse. Derfor, som vist ovenfor, nu det samme Professor klasse forbruger 16 byte mere.

5.5. Dyb størrelse

For at beregne den dybe størrelse skal vi inkludere den fulde størrelse af selve objektet og alle dets samarbejdspartnere. For eksempel til dette enkle scenario:

String ds = "Datastrukturer"; Kursuskursus = nyt kursus (ds);

Den dybe størrelse af Rute forekomst er lig med den lave størrelse af Rute instans selv plus den dybe størrelse af den pågældende Snor eksempel.

Når det er sagt, lad os se, hvor meget plads det er Snor eksempel forbruger:

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

Hver Snor instans indkapsler en char [] (mere om dette senere) og en int hashcode:

java.lang.Strengobjektinterne: OFFSET STØRRELSE TYPE BESKRIVELSE VÆRDI 0 4 (objektoverskrift) 01 00 00 00 4 4 (objektoverskrift) 00 00 00 00 8 4 (objektoverskrift) da 02 00 f8 12 4 tegn [] Streng. værdi [D, a, t, a,, S, t, r, u, c, t, u, r, e, s] 16 4 int String.hash 0 20 4 (tab på grund af næste objektjustering) Instans størrelse: 24 byte Rumtab: 0 byte internt + 4 byte eksternt = 4 bytes i alt

Den overfladiske størrelse af dette Snor forekomst er 24 byte, som inkluderer de 4 byte af cachet hash-kode, 4 byte af char [] reference og andre typiske genstandsudgifter.

For at se den faktiske størrelse på char [], vi kan også analysere klassens layout:

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

Layoutet af char [] ser sådan ud:

[C objekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE VÆRDI 0 4 (objekt header) 01 00 00 00 4 4 (objekt header) 00 00 00 00 8 4 (objekt header) 41 00 00 f8 12 4 (objekt header) 0f 00 00 00 16 30 char [C. N / A 46 2 (tab på grund af den næste objektjustering) Forekomststørrelse: 48 byte Rumtab: 0 byte interne + 2 byte eksterne = 2 bytes i alt

Så vi har 16 byte til Rute eksempel 24 byte til Snor eksempel, og til sidst 48 byte til char []. I alt den dybe størrelse af det Rute eksempel er 88 byte.

Med introduktionen af ​​kompakte strenge i Java 9, blev Snor klasse bruger internt en byte [] at gemme tegnene:

java.lang.Streng objekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE 0 4 (objekt header) 4 4 (objekt header) 8 4 (objekt header) 12 4 byte [] String.value # byte array 16 4 int String.hash 20 1 byte String.coder # encodig 21 3 (tab på grund af den næste objektjustering)

Derfor er det samlede fodaftryk for Java på Java 9+ Rute forekomst vil være 72 byte i stedet for 88 byte.

5.6. Objektgraflayout

I stedet for at analysere klasselayoutet for hvert objekt i en objektgraf separat, kan vi bruge GraphLayout. Med GraphLayot, vi passerer bare startpunktet for objektgrafen, og den rapporterer layoutet for alle tilgængelige objekter fra det startpunkt. På denne måde kan vi beregne den dybe størrelse af startpunktet for grafen.

For eksempel kan vi se det samlede fodaftryk af Rute eksempel som følger:

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

Hvilket udskriver følgende resume:

[e-mail-beskyttet] fodaftryk: COUNT AVG SUM BESKRIVELSE 1 48 48 [C 1 16 16 com.baeldung.objectsize.Course 1 24 24 java.lang.String 3 88 (total)

Det er i alt 88 byte. Det totalSize () metoden returnerer objektets samlede fodaftryk, som er 88 byte:

System.out.println (GraphLayout.parseInstance (kursus) .totalSize ());

6. Instrumentering

For at beregne den objektive overfladestørrelse kan vi også bruge Java-instrumenteringspakken og Java-agenter. Først skal vi oprette en klasse med en premain () metode:

offentlig klasse ObjectSizeCalculator {privat statisk instrumentering instrumentering; offentlig statisk ugyldig premain (String args, Instrumentation inst) {instrumentation = inst; } offentlig statisk lang størrelseOf (Objekt o) {return instrumentation.getObjectSize (o); }}

Som vist ovenfor bruger vi getObjectSize () metode til at finde den objektive overfladestørrelse. Vi har også brug for en manifestfil:

Premain-klasse: com.baeldung.objectsize.ObjectSizeCalculator

Brug derefter dette MANIFEST.MF fil, kan vi oprette en JAR-fil og bruge den som en Java-agent:

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

Endelig, hvis vi kører en kode med -javaagent: /path/to/agent.jar argument, så kan vi bruge sizeOf () metode:

String ds = "Datastrukturer"; Kursuskursus = nyt kursus (ds); System.out.println (ObjectSizeCalculator.sizeOf (kursus));

Dette vil udskrive 16 som den lave størrelse af Rute eksempel.

7. Klassestatistikker

For at se den lave størrelse af objekter i en allerede kørende applikation kan vi se på klassestatistikken ved hjælp af jcmd:

$ jcmd GC.class_stats [output_column]

For eksempel kan vi se hver forekomststørrelse og antal af alle Rute tilfælde:

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

Igen rapporterer dette den lave størrelse af hver Rute eksempel som 16 bytes.

For at se klassestatistikkerne skal vi starte applikationen med -XX: + UnlockDiagnosticVMOptions tuning flag.

8. Heap Dump

Brug af heap-dumps er en anden mulighed for at inspicere forekomststørrelserne i kørende applikationer. På denne måde kan vi se den bevarede størrelse for hver forekomst. For at tage en bunke dump, kan vi bruge jcmd som følgende:

$ jcmd GC.heap_dump [optioner] / sti / til / dump / fil

For eksempel:

$ jcmd 63984 GC.heap_dump -all ~ / dump.hpro

Dette opretter en bunke-dump på det angivne sted. Også med -alle valgmulighed, vil alle tilgængelige og utilgængelige objekter være til stede i bunke-dumpen. Uden denne mulighed udfører JVM en fuld GC, inden den opretter bunke dump.

Efter at have fået bunke dump, kan vi importere det til værktøjer som Visual VM:

Som vist ovenfor er den eneste tilbageholdte størrelse Rute eksempel er 24 byte. Som tidligere nævnt kan den bevarede størrelse være hvor som helst mellem lavvandede (16 byte) og dybe størrelser (88 byte).

Det er også værd at nævne, at Visual VM var en del af Oracle- og Open JDK-distributionerne før Java 9. Dette er dog ikke længere tilfældet med Java 9, og vi bør downloade Visual VM fra dens websted separat.

9. Konklusion

I denne vejledning blev vi fortrolige med forskellige målinger til at måle objektstørrelser i JVM-runtime. Derefter målte vi faktisk instansstørrelser med forskellige værktøjer som JOL, Java Agents og the jcmd kommandolinjeværktøj.

Som sædvanligt er alle eksemplerne tilgængelige på GitHub.