Objektumméretek mérése a JVM-ben

1. Áttekintés

Ebben az oktatóanyagban megnézzük, hogy az egyes objektumok mennyi helyet foglalnak el a Java kupacban.

Először megismerjük a különböző mutatókat az objektumméretek kiszámításához. Ezután megnézünk néhány módot a példányméretek mérésére.

A futásidejű adatterületek memóriaelrendezése általában nem része a JVM specifikációnak, és a végrehajtó belátására bízzák. Ezért minden egyes JVM-megvalósításnak más stratégiája lehet, mint az objektumok és tömbök elrendezése a memóriában. Ez viszont hatással lesz a futásidejű példányméretekre.

Ebben az oktatóanyagban egy konkrét JVM-megvalósításra összpontosítunk: a HotSpot JVM-re.

A JVM és a HotSpot JVM kifejezéseket felváltva is használjuk az oktatóanyagban.

2. Sekély, megtartott és mély tárgyméretek

Az objektumméretek elemzéséhez három különböző mutatót használhatunk: sekély, megtartott és mély méretek.

Az objektum sekély méretének kiszámításakor csak magát az objektumot vesszük figyelembe. Vagyis, ha az objektum hivatkozik más objektumokra, akkor csak a célobjektumok referenciaméretét vesszük figyelembe, nem a tényleges objektumméretüket. Például:

Amint fent látható, a Hármas a példány csak három hivatkozás összege. Kizárjuk a hivatkozott objektumok tényleges méretét, nevezetesen A1, B1, és C1, ebből a méretből.

Ellenkezőleg, egy objektum mély mérete magában foglalja az összes hivatkozott objektum méretét, a sekély méret mellett:

Itt a mély mérete Hármas példány három referenciát tartalmaz, plusz a A1, B1, és C1. Ezért a mély méretek rekurzív jellegűek.

Amikor a GC visszanyeri egy objektum által elfoglalt memóriát, akkor meghatározott mennyiségű memóriát szabadít fel. Ez az összeg az objektum megtartott mérete:

A megtartott méret Hármas csak a példány tartalmazza A1 és C1 amellett, hogy a Hármas maga a példány. Másrészt ez a megtartott méret nem tartalmazza a B1, mivel a Pár példában is van utalás B1.

Néha ezeket az extra hivatkozásokat közvetetten maga a JVM teszi. Ezért a megtartott méret kiszámítása bonyolult feladat lehet.

A megtartott méret jobb megértése érdekében a szemétszállítás szempontjából kell gondolkodnunk. A Hármas példány teszi a A1 és C1 elérhetetlen, de a B1 még mindig elérhető egy másik tárgyon keresztül. A helyzettől függően a megtartott méret bárhol lehet a sekély és a mély méret között.

3. Függőség

Az objektumok vagy tömbök memóriaelrendezésének ellenőrzéséhez a JVM-ben a Java Object Layout (JOL) eszközt fogjuk használni. Ezért hozzá kell adnunk a jol-core függőség:

 org.openjdk.jol jol-core 0.10 

4. Egyszerű adattípusok

Ahhoz, hogy jobban megértsük a bonyolultabb objektumok méretét, először tudnunk kell, hogy az egyes egyszerű adattípusok mennyi helyet foglalnak el. Ehhez megkérhetjük a Java Memory Layout-t vagy a JOL-t, hogy nyomtassa ki a virtuális gép információkat:

System.out.println (VM.current (). Részletek ());

A fenti kód az alábbiak szerint nyomtatja ki az egyszerű adattípus-méreteket:

# 64 bites HotSpot virtuális gép futtatása. # Tömörített opció használata 3 bites váltással. # Tömörített klass használata 3 bites váltással. # Az objektumok 8 bájttal vannak igazítva. # A mezők méretei típus szerint: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bájt] # Tömb elemméretek: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bájt ]

Tehát itt vannak a JVM minden egyszerű adattípusának helyigénye:

  • Az objektumhivatkozások 4 bájtot fogyasztanak
  • logikai és byte értékek 1 bájtot fogyasztanak
  • rövid és char az értékek 2 bájtot fogyasztanak
  • int és úszó az értékek 4 bájtot fogyasztanak
  • hosszú és kettős az értékek 8 bájtot fogyasztanak

Ez igaz a 32 bites architektúrákra és a 64 bites architektúrákra is, tömörített referenciákkal.

Érdemes megemlíteni azt is, hogy minden adattípus ugyanolyan memóriát fogyaszt, ha tömbkomponens-típusként használják.

4.1. Tömörítetlen hivatkozások

Ha letiltjuk a tömörített hivatkozásokat a -XX: -UseCompressedOops tuning flag, akkor a méretigény megváltozik:

# Az objektumok 8 bájttal vannak igazítva. # A mezők méretei típus szerint: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bájt] # Tömb elemméretek: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bájt ]

Most az objektum hivatkozások 4 bájt helyett 8 bájtot fognak felemészteni. A többi adattípus továbbra is ugyanannyi memóriát fogyaszt.

Sőt, a HotSpot JVM szintén nem tudja használni a tömörített hivatkozásokat, ha a kupac mérete meghaladja a 32 GB-ot (hacsak nem változtatjuk meg az objektum igazítását).

A lényeg az, hogy ha kifejezetten letiltjuk a tömörített hivatkozásokat, vagy a halom mérete meghaladja a 32 GB-ot, akkor az objektum-referenciák 8 bájtot fogyasztanak.

Most, hogy ismerjük az alapvető adattípusok memóriafogyasztását, számítsuk ki bonyolultabb objektumok esetén.

5. Komplex objektumok

Az összetett objektumok méretének kiszámításához vegyünk egy tipikus professzor és a tanfolyam kapcsolatát:

nyilvános osztály Tanfolyam {private String name; // konstruktor}

Minden egyes Egyetemi tanár, a személyes adatok mellett listája lehet Tanfolyams:

public class professzor {private String név; privát boolean tenured; privát lista tanfolyamok = new ArrayList (); magán int szint; privát LocalDate birthDay; privát dupla lastEvaluation; // konstruktor}

5.1. Sekély méret: a Tanfolyam Osztály

A. Sekély mérete Tanfolyam osztálypéldányoknak tartalmazniuk kell egy 4 bájtos objektum hivatkozást (a név mező), valamint valamilyen tárgy rezsi. Ezt a feltételezést a JOL segítségével ellenőrizhetjük:

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

Ez a következőket nyomtatja ki:

A tantárgy belső elemei: OFFSET MÉRET TÍPUS LEÍRÁS ÉRTÉK 0 12 (objektum fejléc) N / A 12 4 java.lang.String Course.name N / A Példányméret: 16 bájt Helyveszteség: 0 bájt belső + 0 bájt külső = 0 bájt összesen

Amint fent látható, a sekély méret 16 bájt, beleértve a 4 bájtos objektum hivatkozást a fájlra név mező plusz az objektum fejléc.

5.2. Sekély méret: a Egyetemi tanár Osztály

Ha ugyanazt a kódot futtatjuk a Egyetemi tanár osztály:

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

Ezután a JOL kinyomtatja a Egyetemi tanár osztály, mint a következő:

Professzor objektum belső részei: OFFSET MÉRET TÍPUS LEÍRÁS ÉRTÉK 0 12 (objektum fejléc) N / A 12 4 int Professzor.szint N / A 16 8 kettős Professor.lastElértékelés N / A 24 1 logikai Professzor.tartós N / A 25 3 (igazítás / kitöltési rés) 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 Példányméret: 40 bájt Helyvesztés: 3 bájt belső + 0 bájt külső = 3 bájt összesen

Ahogy valószínűleg vártuk, a beágyazott mezők 25 bájtot fogyasztanak:

  • Három objektumreferencia, amelyek mindegyike 4 bájtot fogyaszt. Tehát összesen 12 bájt más objektumok hivatkozására
  • Egy int amely 4 bájtot fogyaszt
  • Egy logikai amely 1 bájtot fogyaszt
  • Egy kettős amely 8 bájtot fogyaszt

Ha hozzáadjuk az objektum fejlécének 12 bájtos felső részét, valamint 3 bájt igazítási kitöltést, a sekély méret 40 bájt.

A legfontosabb elvihetőség itt az egyes objektumok beágyazott állapota mellett a különböző objektumméretek kiszámításakor figyelembe kell venni az objektum fejlécét és az igazítási párnákat.

5.3. Sekély méret: egy példány

A mérete() A JOL metódusa sokkal egyszerűbb módszert kínál az objektumpéldány sekély méretének kiszámításához. Ha a következő részletet futtatjuk:

String ds = "Adatszerkezetek"; Tanfolyam = új tanfolyam (ds); System.out.println ("A sekély méret:" + VM.current (). SizeOf (course));

A sekély méretet a következőképpen fogja kinyomtatni:

A sekély méret: 16

5.4. Tömörítetlen méret

Ha letiltjuk a tömörített hivatkozásokat, vagy a halom 32 GB-nál többet használunk, akkor a sekély méret megnő:

Professzor objektum belső részei: OFFSET MÉRET TÍPUS LEÍRÁSI ÉRTÉK 0 16 (objektum fejléc) N / A 16 8 kettős Professor.lastEvaluation N / A 24 4 int Professor.level N / A 28 1 logikai Professzor.tartós N / A 29 3 (igazítás / kitöltési rés) 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 Példányméret: 56 bájt Helyvesztés: 3 bájt belső + 0 bájt külső = 3 bájt összesen

Ha a tömörített hivatkozások le vannak tiltva, az objektumfejléc és az objektumhivatkozások több memóriát emésztenek fel. Ezért, mint a fent látható, most ugyanaz Egyetemi tanár osztály további 16 bájtot fogyaszt.

5.5. Mély méret

A mély méret kiszámításához magának az objektumnak és az összes munkatársának a teljes méretét bele kell foglalnunk. Például ehhez az egyszerű forgatókönyvhöz:

String ds = "Adatszerkezetek"; Tanfolyam = új tanfolyam (ds);

A mély mérete Tanfolyam példány megegyezik a Tanfolyam maga az adott eset plusz az adott méret mély mérete Húr példa.

Ennek elmondásával nézzük meg, hogy ez mennyi hely Húr példány fogyaszt:

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

Minden egyes Húr példány befogadja a char [] (erről bővebben később) és egy int hash kód:

java.lang.Az objektum belső elemei: OFFSET MÉRET TÍPUS LEÍRÁSI ÉRTÉK 0 4 (objektum fejléc) 01 00 00 00 4 4 (objektum fejléc) 00 00 00 00 8 4 (objektum fejléc) da 02 00 f8 12 4 char [] karakterlánc. érték [D, a, t, a,, S, t, r, u, c, t, u, r, e, s] 16 4 int String.hash 0 20 4 (veszteség a következő objektumillesztés miatt) Példány méret: 24 bájt Térveszteség: 0 bájt belső + 4 bájt külső = 4 bájt összesen

Ennek sekély mérete Húr a példány 24 bájt, amely magában foglalja a 4 bájt gyorsítótárazott kivonatkódot, 4 bájt a char [] referencia és egyéb tipikus tárgyi rezsi.

A. Tényleges méretének megtekintéséhez char [], elemezhetjük az osztálykiosztását is:

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

Az elrendezés char [] így néz ki:

[C objektum belső részei: OFFSET MÉRET TÍPUS LEÍRÁS ÉRTÉK 0 4 (objektum fejléc) 01 00 00 00 4 4 (objektum fejléc) 00 00 00 00 8 4 (objektum fejléc) 41 00 00 f8 12 4 (objektum fejléc) 0f 00 00 00 16 30 char [C. N / A 46 2 (veszteség a következő objektum igazítás miatt) Példányméret: 48 bájt Helyveszteség: 0 bájt belső + 2 bájt külső = 2 bájt összesen

Tehát van 16 bájtunk a Tanfolyam például 24 bájt a Húr például, és végül 48 bájt a char []. Összességében ennek mély mérete Tanfolyam a példány 88 bájt.

A Java 9 kompakt húrok bevezetésével az Húr osztály belsőleg használja a byte[] a karakterek tárolásához:

java.lang.String objektum belseje: OFFSET MÉRET TÍPUS LEÍRÁSA 0 4 (objektum fejléc) 4 4 (objektum fejléc) 8 4 (objektum fejléc) 12 4 bájt [] String.value # byte tömb 16 4 int String.hash 20 1 bájt String.coder # encodig 21 3 (veszteség a következő objektum igazítás miatt)

Ezért a Java 9+ rendszeren a Tanfolyam példány 72 bájt lesz 88 bájt helyett.

5.6. Objektumdiagram-elrendezés

Ahelyett, hogy az egyes objektumok osztályelrendezését külön elemeznénk egy objektumgrafikonban, használhatjuk a GraphLayout. Val vel GraphLayot, csak átadjuk az objektumdiagram kiindulópontját, és az az összes elérhető objektum elrendezését beszámolja erről a kiindulópontról. Így kiszámíthatjuk a grafikon kezdőpontjának mély méretét.

Például láthatjuk a Tanfolyam például az alábbiak szerint:

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

Amely kinyomtatja a következő összefoglalót:

[e-mail védett] lábnyom: SZÁMA ÁTLAGOS ÖSSZEFOGLALÓ 1 48 48 [C 1 16 16 com.baeldung.objectsize.Pálya 1 24 24 java.lang.String 3 88 (összesen)

Ez összesen 88 bájt. A teljes méret() metódus adja vissza az objektum teljes lábnyomát, amely 88 bájt:

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

6. Hangszerelés

Az objektum sekély méretének kiszámításához használhatjuk a Java műszercsomagot és a Java ügynököket is. Először létre kell hoznunk egy osztályt a-val premain () módszer:

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

A fentiek szerint a getObjectSize () módszer az objektum sekély méretének megtalálásához. Szükségünk van egy manifeszt fájlra is:

Premain-osztály: com.baeldung.objectsize.ObjectSizeCalculator

Akkor használja ezt MANIFEST.MF fájlt, létrehozhatunk egy JAR fájlt, és Java ügynökként használhatjuk:

$ jar cmf MANIFEST.MF agent.jar * .osztály

Végül, ha bármilyen kódot futtatunk a -javaagent: /path/to/agent.jar argumentumot, akkor használhatjuk a mérete() módszer:

String ds = "Adatszerkezetek"; Tanfolyam = új tanfolyam (ds); System.out.println (ObjectSizeCalculator.sizeOf (tanfolyam));

Ez 16-at nyomtat a sekély méretként Tanfolyam példa.

7. Osztálystatisztika

Az objektumok sekély méretének megtekintéséhez egy már futó alkalmazásban megnézhetjük az osztály statisztikáit a jcmd:

$ jcmd GC.class_stats [output_columns]

Például láthatjuk az egyes példányok méretét és számát Tanfolyam példányok:

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

Ez ismét mindegyik sekély méretét jelenti Tanfolyam például 16 bájtként.

Az osztály statisztikájának megtekintéséhez el kell indítanunk az alkalmazást a -XX: + UnlockDiagnosticVMOptions tuning zászló.

8. Halomdomb

A halomdombok használata egy másik lehetőség a futó alkalmazások példányméreteinek ellenőrzésére. Így láthatjuk az egyes példányok megtartott méretét. Halomlerakáshoz használhatjuk a jcmd az alábbiak szerint:

$ jcmd GC.heap_dump [opciók] / elérési út / a / dump / fájlba

Például:

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

Ez egy kupacdeppert hoz létre a megadott helyen. Továbbá a -minden opció esetén az összes elérhető és elérhetetlen objektum jelen lesz a kupaclerakóban. Ezen opció nélkül a JVM teljes GC-t hajt végre, mielőtt létrehozná a kupacdumpot.

A kupac dump megszerzése után importálhatjuk olyan eszközökbe, mint a Visual VM:

Amint fentebb látható, az egyetlen megtartott mérete Tanfolyam a példány 24 bájt. Mint korábban említettük, a megtartott méret bárhol lehet a sekély (16 bájt) és a mély (88 bájt) között.

Érdemes megemlíteni azt is, hogy a Visual VM az Oracle és az Open JDK disztribúció része volt a Java 9 előtt. Ez azonban a Java 9 esetében már nem így van, ezért a Visual VM-et külön le kell töltenünk a weboldaláról.

9. Következtetés

Ebben az oktatóanyagban megismerkedtünk a JVM futásidejű objektumméretek mérésének különböző mutatóival. Ezt követően valóban megmértük a példányméreteket különféle eszközökkel, például a JOL-nal, a Java ügynökökkel és a jcmd parancssori segédprogram.

Szokás szerint az összes példa elérhető a GitHubon.