Måle objektstørrelser i JVM

1. Oversikt

I denne opplæringen skal vi se hvor mye plass hvert objekt bruker i Java-bunken.

Først blir vi kjent med forskjellige beregninger for å beregne objektstørrelser. Deretter skal vi se noen måter å måle forekomststørrelser på.

Vanligvis er minneoppsettet for kjøretidsdataområder ikke en del av JVM-spesifikasjonen og overlates til implementeringsmyndighetens skjønn. Derfor kan hver JVM-implementering ha en annen strategi for å utforme objekter og matriser i minnet. Dette vil igjen påvirke forekomststørrelsene ved kjøretid.

I denne opplæringen fokuserer vi på en spesifikk JVM-implementering: HotSpot JVM.

Vi bruker også JVM og HotSpot JVM termer om hverandre gjennom hele opplæringen.

2. Størrelser på grunne, beholdte og dype objekter

For å analysere objektstørrelsene kan vi bruke tre forskjellige beregninger: Grunne, beholdte og dype størrelser.

Når vi beregner den grunne størrelsen på et objekt, tar vi bare hensyn til selve objektet. Det vil si at hvis objektet har referanser til andre objekter, tar vi bare i betraktning referansestørrelsen til målobjektene, ikke deres faktiske objektstørrelse. For eksempel:

Som vist ovenfor, er den grunne størrelsen på Trippel forekomst er bare en sum av tre referanser. Vi ekskluderer den faktiske størrelsen på de henviste objektene, nemlig A1, B1, og C1, fra denne størrelsen.

Tvert imot, den dype størrelsen på et objekt inkluderer størrelsen på alle henviste objekter, i tillegg til den grunne størrelsen:

Her er den dype størrelsen på Trippel forekomst inneholder tre referanser pluss den faktiske størrelsen på A1, B1, og C1. Derfor er dype størrelser rekursive i naturen.

Når GC gjenvinner minnet okkupert av et objekt, frigjør det en bestemt mengde minne. Beløpet er den beholdte størrelsen på det objektet:

Den beholdte størrelsen på Trippel forekomst inkluderer bare A1 og C1 i tillegg til det Trippel forekomst selv. På den annen side inkluderer ikke denne beholdte størrelsen B1, siden Par forekomst har også en referanse til B1.

Noen ganger blir disse ekstra referansene indirekte laget av JVM selv. Derfor kan beregning av beholdt størrelse være en komplisert oppgave.

For å bedre forstå den beholdte størrelsen, bør vi tenke i forhold til søppeloppsamlingen. Samler inn Trippel eksempel gjør A1 og C1 uoppnåelig, men den B1 er fremdeles tilgjengelig gjennom et annet objekt. Avhengig av situasjonen kan den beholdte størrelsen være hvor som helst mellom den grunne og dype størrelsen.

3. Avhengighet

For å inspisere minneoppsettet til objekter eller matriser i JVM, skal vi bruke JOL-verktøyet (Java Object Layout). Derfor må vi legge til jol-core avhengighet:

 org.openjdk.jol jol-core 0.10 

4. Enkle datatyper

For å få en bedre forståelse av størrelsen på mer komplekse objekter, bør vi først vite hvor mye plass hver enkel datatype bruker. For å gjøre det kan vi be Java Memory Layout eller JOL om å skrive ut VM-informasjonen:

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

Ovennevnte kode vil skrive ut de enkle datatypestørrelsene som følger:

# Kjører 64-biters HotSpot VM. # Bruke komprimert oop med 3-bits skift. # Bruke komprimert klass med 3-bits skift. # Objekter er 8 byte justert. # Feltstørrelser etter type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [byte] # Array-elementstørrelser: 4, 1, 1, 2, 2, 4, 4, 8, 8 [byte ]

Så her er plasskravene for hver enkle datatype i JVM:

  • Objektreferanser bruker 4 byte
  • boolsk og byte verdier bruker 1 byte
  • kort og røye verdier bruker 2 byte
  • int og flyte verdier bruker 4 byte
  • lang og dobbelt verdier bruker 8 byte

Dette gjelder i 32-biters arkitekturer og også 64-biters arkitekturer med komprimerte referanser.

Det er også verdt å nevne at alle datatyper bruker like mye minne når de brukes som matrikkomponenttyper.

4.1. Ukomprimerte referanser

Hvis vi deaktiverer komprimerte referanser via -XX: -UseCompressedOops tuning flagg, vil størrelseskravene endres:

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

Nå vil objektreferanser forbruke 8 byte i stedet for 4 byte. De resterende datatypene bruker fortsatt like mye minne.

Videre kan HotSpot JVM heller ikke bruke de komprimerte referansene når haugestørrelsen er mer enn 32 GB (med mindre vi endrer objektjusteringen).

Poenget er at hvis vi deaktiverer komprimerte referanser eksplisitt eller haugestørrelsen er mer enn 32 GB, vil objektreferansene forbruke 8 byte.

Nå som vi kjenner minneforbruket til grunnleggende datatyper, la oss beregne det for mer komplekse objekter.

5. Komplekse objekter

For å beregne størrelsen på komplekse objekter, la oss vurdere en typisk professor til kursforhold:

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

Hver Professor, i tillegg til de personlige opplysningene, kan ha en liste over Kurss:

offentlig klasse professor {privat strengnavn; privat boolsk fast tid; private List-kurs = ny ArrayList (); privat int nivå; privat LocalDate fødselsdag; privat dobbel lastEvaluation; // konstruktør}

5.1. Grunn størrelse: den Kurs Klasse

Den grunne størrelsen på Kurs klasseinstanser skal inneholde en 4-byte objektreferanse (for Navn felt) pluss noe overhead. Vi kan sjekke denne antagelsen ved hjelp av JOL:

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

Dette vil skrive ut følgende:

Internt for kursobjekt: OFFSET STØRRELSE TYPE BESKRIVELSE VERDI 0 12 (objektoverskrift) Ikke relevant 12 4 java.lang.Streng Kursnavn Navn Ikke relevant Forekomststørrelse: 16 byte Romtap: 0 byte internt + 0 byte eksternt = 0 byte totalt

Som vist ovenfor er den grunne størrelsen 16 byte, inkludert en 4 byte objektreferanse til Navn felt pluss objektoverskriften.

5.2. Grunn størrelse: den Professor Klasse

Hvis vi kjører den samme koden for Professor klasse:

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

Deretter vil JOL skrive ut minneforbruket for Professor klasse som følgende:

Professor objekter internt: OFFSET STØRRELSE TYPE BESKRIVELSE VERDI 0 12 (objektoverskrift) N / A 12 4 int Professor.nivå N / A 16 8 dobbel Professor. SisteEvaluering N / A 24 1 boolsk professor. Holdet N / A 25 3 (justering / padding gap) 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 byte Plass tap: 3 byte interne + 0 byte eksterne = totalt 3 byte

Som vi sannsynligvis forventet, bruker de innkapslede feltene 25 byte:

  • Tre objektreferanser, som hver bruker 4 byte. Så 12 byte totalt for å referere til andre objekter
  • En int som bruker 4 byte
  • En boolsk som bruker 1 byte
  • En dobbelt som bruker 8 byte

Når du legger til 12 bytes overhead på objektoverskriften pluss 3 byte med justeringspolstring, er den grunne størrelsen 40 byte.

Nøkkelen til takeaway her er, i tillegg til den innkapslede tilstanden til hvert objekt, bør vi vurdere objektoverskriften og justeringspolstrene når vi beregner forskjellige objektstørrelser.

5.3. Grunn størrelse: en forekomst

De størrelsen av() metoden i JOL gir en mye enklere måte å beregne den grunne størrelsen på en objektforekomst. Hvis vi kjører følgende kodebit:

String ds = "Datastrukturer"; Kurskurs = nytt Kurs (ds); System.out.println ("Den grunne størrelsen er:" + VM.current (). SizeOf (kurs));

Den skriver ut den grunne størrelsen som følger:

Den grunne størrelsen er: 16

5.4. Ukomprimert størrelse

Hvis vi deaktiverer komprimerte referanser eller bruker mer enn 32 GB av haugen, vil den grunne størrelsen øke:

Professor objekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE VERDI 0 16 (objektoverskrift) N / A 16 8 dobbel Professor. SisteEvaluering N / A 24 4 int Professor.nivå N / A 28 1 boolsk professor. Holdet N / A 29 3 (justering / polstringsgap) 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 byte Plasstap: 3 byte interne + 0 byte eksterne = totalt 3 byte

Når de komprimerte referansene er deaktivert, vil objektoverskriften og objektreferansene forbruke mer minne. Derfor, som vist ovenfor, nå det samme Professor klasse bruker 16 byte til.

5.5. Dyp størrelse

For å beregne den dype størrelsen, bør vi ta med hele størrelsen på selve objektet og alle dets samarbeidspartnere. For eksempel for dette enkle scenariet:

String ds = "Datastrukturer"; Kurskurs = nytt Kurs (ds);

Den dype størrelsen på Kurs forekomst er lik den grunne størrelsen på Kurs forekomsten i seg selv pluss den dype størrelsen på akkurat det String forekomst.

Når det er sagt, la oss se hvor mye plass det String forekomst forbruker:

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

Hver String forekomst innkapsler en røye [] (mer om dette senere) og et int hashcode:

java.lang.Strengobjekt internt: OFFSET STØRRELSE TYPE BESKRIVELSE VERDI 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. verdi [D, a, t, a,, S, t, r, u, c, t, u, r, e, s] 16 4 int Streng.hash 0 20 4 (tap på grunn av neste objektjustering) Forekomst størrelse: 24 byte Plass tap: 0 byte internt + 4 byte eksternt = 4 byte totalt

Den grunne størrelsen på dette String forekomst er 24 byte, som inkluderer 4 byte med hurtigbufret hash-kode, 4 byte av røye [] referanse, og andre typiske objekter overhead.

For å se den faktiske størrelsen på røye [], vi kan analysere klassens layout også:

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

Oppsettet til røye [] ser slik ut:

[C objekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE VERDI 0 4 (objektoverskrift) 01 00 00 00 4 4 (objektoverskrift) 00 00 00 00 8 4 (objektoverskrift) 41 00 00 f8 12 4 (objektoverskrift) 0f 00 00 00 16 30 røye [C. N / A 46 2 (tap på grunn av neste objektjustering) Forekomststørrelse: 48 byte Romtap: 0 byte internt + 2 byte eksternt = 2 byte totalt

Så vi har 16 byte for Kurs for eksempel 24 byte for String eksempel, og til slutt 48 byte for røye []. Totalt sett den dype størrelsen på det Kurs forekomst er 88 byte.

Med introduksjonen av kompakte strenger i Java 9, ble String klassen bruker internt en byte [] for å lagre karakterene:

java.lang.Strengobjekt interner: OFFSET STØRRELSE TYPE BESKRIVELSE 0 4 (objektoverskrift) 4 4 (objektoverskrift) 8 4 (objektoverskrift) 12 4 byte [] Streng.verdi # byteoppstillingen 16 4 int String.hash 20 1 byte String.coder # encodig 21 3 (tap på grunn av neste objektjustering)

Derfor, på Java 9+, blir det totale fotavtrykket til Kurs forekomst vil være 72 byte i stedet for 88 byte.

5.6. Objektgrafoppsett

I stedet for å analysere klasselayouten til hvert objekt i et objektgraf separat, kan vi bruke GraphLayout. Med GraphLayot, vi passerer bare startpunktet til objektgrafen, og den rapporterer utformingen av alle tilgjengelige objekter fra det startpunktet. På denne måten kan vi beregne den dype størrelsen på startpunktet til grafen.

For eksempel kan vi se det totale fotavtrykket til Kurs eksempel som følger:

System.out.println (GraphLayout.parseInstance (kurs). Til Fotavtrykk ());

Som skriver ut følgende sammendrag:

[e-postbeskyttet] fotavtrykk: 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 totalt 88 byte. De Total størrelse() metoden returnerer det totale fotavtrykket til objektet, som er 88 byte:

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

6. Instrumentering

For å beregne den lave størrelsen på et objekt, kan vi også bruke Java-instrumentasjonspakken og Java-agenter. Først bør vi lage en klasse med en premain () metode:

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

Som vist ovenfor, bruker vi getObjectSize () metode for å finne den grunne størrelsen på et objekt. Vi trenger også en manifestfil:

Premain-klasse: com.baeldung.objectsize.ObjectSizeCalculator

Bruk deretter dette MANIFEST.MF fil, kan vi opprette en JAR-fil og bruke den som en Java-agent:

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

Til slutt, hvis vi kjører noen kode med -javaagent: /path/to/agent.jar argument, så kan vi bruke størrelsen av() metode:

String ds = "Datastrukturer"; Kurskurs = nytt Kurs (ds); System.out.println (ObjectSizeCalculator.sizeOf (kurs));

Dette vil skrive ut 16 som den lave størrelsen på Kurs forekomst.

7. Klassestatistikker

For å se den lave størrelsen på objekter i et allerede kjørende program, kan vi ta en titt på klassestatistikken ved hjelp av jcmd:

$ jcmd GC.class_stats [output_column]

For eksempel kan vi se hver forekomststørrelse og antall av alle Kurs forekomster:

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

Igjen rapporterer dette den grunne størrelsen på hver Kurs forekomst som 16 byte.

For å se klassestatistikken, bør vi starte applikasjonen med -XX: + UnlockDiagnosticVMOptions innstillingsflagg.

8. Heap Dump

Å bruke heap dumps er et annet alternativ for å inspisere forekomststørrelser i applikasjoner som kjører. På denne måten kan vi se den beholdte størrelsen for hver forekomst. For å ta en haug dump kan vi bruke jcmd som følgende:

$ jcmd GC.heap_dump [opsjoner] / bane / til / dump / fil

For eksempel:

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

Dette vil skape en heap dump på det angitte stedet. Også med -alle alternativ, vil alle tilgjengelige og utilgjengelige gjenstander være til stede i dyngdumpen. Uten dette alternativet vil JVM utføre en fullstendig GC før du oppretter dyngdumpen.

Etter å ha fått heap dump, kan vi importere den til verktøy som Visual VM:

Som vist ovenfor, beholdes størrelsen på den eneste Kurs forekomst er 24 byte. Som nevnt tidligere kan den beholdte størrelsen være hvor som helst mellom grunne (16 byte) og dype størrelser (88 byte).

Det er også verdt å nevne at Visual VM var en del av Oracle og Open JDK-distribusjonene før Java 9. Dette er imidlertid ikke lenger tilfelle med Java 9, og vi bør laste ned Visual VM fra nettstedet separat.

9. Konklusjon

I denne opplæringen ble vi kjent med forskjellige beregninger for å måle objektstørrelser i JVM-kjøretiden. Etter det målte vi faktisk forekomststørrelser med forskjellige verktøy som JOL, Java Agents og jcmd kommandolinjeverktøy.

Som vanlig er alle eksemplene tilgjengelige på GitHub.