Mjerenje veličina objekata u JVM-u

1. Pregled

U ovom uputstvu vidjet ćemo koliko prostora svaki objekt troši na Java hrpi.

Prvo ćemo se upoznati s različitim mjernim podacima za izračunavanje veličina objekata. Zatim ćemo vidjeti nekoliko načina za mjerenje veličina instance.

Uobičajeno, raspored memorije područja izvođenja podataka nije dio JVM specifikacije i prepušten je diskreciji implementatora. Stoga svaka JVM implementacija može imati drugačiju strategiju raspoređivanja objekata i nizova u memoriji. To će, pak, utjecati na veličine instance tijekom izvođenja.

U ovom se vodiču usredotočujemo na jednu specifičnu implementaciju JVM-a: HotSpot JVM.

Također koristimo izraze JVM i HotSpot JVM naizmjenično tijekom cijelog vodiča.

2. Plitke, zadržane i duboke veličine predmeta

Za analizu veličina predmeta možemo koristiti tri različite metrike: plitke, zadržane i duboke veličine.

Pri izračunavanju plitke veličine predmeta, uzimamo u obzir samo sam objekt. Odnosno, ako objekt ima reference na druge objekte, uzimamo u obzir samo referentnu veličinu ciljnih objekata, a ne njihovu stvarnu veličinu. Na primjer:

Kao što je gore prikazano, plitka veličina Utrostručiti Primjer je samo zbroj tri reference. Isključujemo stvarnu veličinu navedenih objekata, naime A1, B1, i C1, od ove veličine.

Baš suprotno, duboka veličina objekta, uz plitku veličinu, uključuje i veličinu svih navedenih predmeta:

Ovdje je duboka veličina Utrostručiti instanca sadrži tri reference plus stvarnu veličinu A1, B1, i C1. Stoga su duboke veličine rekurzivne prirode.

Kada GC povrati memoriju koju zauzima objekt, oslobađa određenu količinu memorije. Taj je iznos zadržana veličina tog predmeta:

Zadržana veličina Utrostručiti instanca uključuje samo A1 i C1 uz to Utrostručiti sama instanca. S druge strane, ova zadržana veličina ne uključuje B1, od Par instanca također ima referencu na B1.

Ponekad JVM izravno upućuje na ove dodatne reference. Stoga izračunavanje zadržane veličine može biti složen zadatak.

Da bismo bolje razumjeli zadržanu veličinu, trebali bismo razmišljati u smislu odvoza smeća. Prikupljanje Utrostručiti instanca čini A1 i C1 nedostižno, ali B1 je još uvijek dostupan putem drugog objekta. Ovisno o situaciji, zadržana veličina može biti bilo gdje između plitke i duboke veličine.

3. Ovisnost

Da bismo pregledali raspored memorije objekata ili polja u JVM-u, upotrijebit ćemo alat Java Layout Layout (JOL). Stoga ćemo morati dodati jol-core ovisnost:

 org.openjdk.jol jol-core 0.10 

4. Jednostavne vrste podataka

Da bismo bolje razumjeli veličinu složenijih objekata, prvo bismo trebali znati koliko prostora zauzima jedna jednostavna vrsta podataka. Da bismo to učinili, možemo zatražiti Java Memory Layout ili JOL da ispišu podatke o VM-u:

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

Gornji kôd ispisat će jednostavne veličine tipa podataka kako slijedi:

# Pokretanje 64-bitnog HotSpot VM-a. # Korištenje komprimiranog oop-a s 3-bitnim pomakom. # Upotreba komprimiranog klasa s 3-bitnim pomakom. # Objekti su poravnati 8 bajtova. # Veličine polja prema vrsti: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bajtova] # Veličine elemenata niza: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bajtova ]

Dakle, ovdje su prostorni zahtjevi za svaku jednostavnu vrstu podataka u JVM-u:

  • Reference objekata troše 4 bajta
  • boolean i bajt vrijednosti troše 1 bajt
  • kratak i ugljen vrijednosti troše 2 bajta
  • int i plutati vrijednosti troše 4 bajta
  • dugo i dvostruko vrijednosti troše 8 bajtova

To vrijedi za 32-bitne arhitekture i također za 64-bitne arhitekture s komprimiranim referencama na snazi.

Također je vrijedno spomenuti da svi tipovi podataka troše jednaku količinu memorije kada se koriste kao tipovi komponenata niza.

4.1. Nekomprimirane reference

Ako onemogućimo komprimirane reference putem -XX: -UseCompressedOops zastavu za podešavanje, tada će se zahtjevi za veličinom promijeniti:

# Objekti su poravnati 8 bajtova. # Veličine polja prema tipu: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bajtova] # Veličine elemenata niza: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bajtova ]

Sada će reference objekata trošiti 8 bajtova umjesto 4 bajta. Preostale vrste podataka i dalje troše istu količinu memorije.

Štoviše, HotSpot JVM također ne može koristiti komprimirane reference kada je veličina hrpe veća od 32 GB (osim ako ne promijenimo poravnanje objekta).

Dno crta je ako izričito onemogućimo komprimirane reference ili je veličina hrpe veća od 32 GB, reference objekta trošit će 8 bajtova.

Sad kad znamo potrošnju memorije za osnovne tipove podataka, izračunajmo je za složenije objekte.

5. Složeni objekti

Da bismo izračunali veličinu složenih predmeta, uzmimo u obzir tipičnog odnosa profesora i kolegija:

javni tečaj {privatni naziv niza; // konstruktor}

Svaki Profesor, osim osobnih podataka, može imati i popis Tečajs:

profesor iz javne klase {ime privatnog niza; private boolean tenured; privatni tečajevi s popisa = novi ArrayList (); privatna int razina; privatni lokalni datum rođenja; privatna dvostruka lastEvaluacija; // konstruktor}

5.1. Plitka veličina: Tečaj Razred

Plitka veličina Tečaj instance klase trebale bi sadržavati 4-bajtnu referencu na objekt (za Ime polje) plus neki objekt iznad glave. Ovu pretpostavku možemo provjeriti pomoću JOL-a:

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

Ovo će ispisati sljedeće:

Unutrašnjost predmeta predmeta: OFFSET VELIČINA VRSTA OPIS VRIJEDNOST 0 12 (zaglavlje objekta) N / A 12 4 java.lang.String Naziv predmeta N / A Veličina instance: 16 bajta Prostorni gubici: 0 bajta unutarnjih + 0 bajta vanjskih = 0 bajtova

Kao što je prikazano gore, plitka veličina je 16 bajtova, uključujući referencu objekta od 4 bajta Ime polje plus zaglavlje objekta.

5.2. Plitka veličina: Profesor Razred

Ako pokrenemo isti kod za Profesor razred:

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

Tada će JOL ispisati potrošnju memorije za Profesor razred poput sljedećeg:

Unutarnji dijelovi profesora: PROMIJENI VELIČINU VRSTA OPIS VRIJEDNOST 0 12 (zaglavlje objekta) N / A 12 4 int Professor.level N / A 16 8 dvostruki Professor.lastEvaluation N / A 24 1 boolean Professor.tenured N / A 25 3 (alignment / 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 Veličina instance: 40 bytes Prostorni gubici: 3 bajta interno + 0 bajta vanjsko = ukupno 3 bajta

Kao što smo vjerojatno i očekivali, enkapsulirana polja troše 25 bajtova:

  • Tri reference objekta, od kojih svaka troši 4 bajta. Dakle, ukupno 12 bajtova za upućivanje na druge objekte
  • Jedan int koji troši 4 bajta
  • Jedan boolean koji troši 1 bajt
  • Jedan dvostruko koji troši 8 bajtova

Dodavanjem 12 bajtova iznad glave zaglavlja objekta plus 3 bajta poravnanja, plitka veličina iznosi 40 bajtova.

Ključno iznošenje ovdje je, uz enkapsulirano stanje svakog objekta, trebali bismo uzeti u obzir zaglavlja i poravnanja objekta prilikom izračuna različitih veličina objekta.

5.3. Plitka veličina: primjerak

The veličina() metoda u JOL-u pruža mnogo jednostavniji način izračuna plitke veličine instance objekta. Ako pokrenemo sljedeći isječak:

Niz ds = "Strukture podataka"; Tečaj tečaja = novi tečaj (ds); System.out.println ("Plitka veličina je:" + VM.current (). SizeOf (naravno));

Ispisat će plitku veličinu na sljedeći način:

Plitka veličina je: 16

5.4. Nekomprimirana veličina

Ako onemogućimo komprimirane reference ili upotrijebimo više od 32 GB hrpe, plitka veličina će se povećati:

Unutarnji dijelovi profesora: PROMIJENI VELIČINU TIP OPIS VRIJEDNOST 0 16 (zaglavlje objekta) N / A 16 8 dvostruko Professor.lastEvaluation N / A 24 4 int Professor.level N / A 28 1 boolean Professor.tenured N / A 29 3 (alignment / padding jaz) 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 Veličina instance: 56 bytes Prostorni gubici: 3 bajta interno + 0 bajta vanjsko = ukupno 3 bajta

Kada su komprimirane reference onemogućene, zaglavlje objekta i reference objekta trošit će više memorije. Stoga, kao što je gore prikazano, sada isto Profesor klasa troši još 16 bajtova.

5.5. Duboka veličina

Da bismo izračunali duboku veličinu, trebali bismo uključiti punu veličinu samog predmeta i svih njegovih suradnika. Na primjer, za ovaj jednostavan scenarij:

Niz ds = "Strukture podataka"; Tečaj tečaja = novi tečaj (ds);

Duboka veličina Tečaj primjer je jednak plitkoj veličini Tečaj sama instanca plus duboka veličina tog određenog Niz primjer.

Kad se to kaže, da vidimo koliko je to prostora Niz instanca troši:

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

Svaki Niz instanca enkapsulira a char [] (o tome više kasnije) i an int hashcode:

java.lang.String unutrašnjosti objekta: OFFSET VELIČINA VRSTA OPIS VRIJEDNOST 0 4 (zaglavlje objekta) 01 00 00 00 4 4 (zaglavlje objekta) 00 00 00 00 8 4 (zaglavlje objekta) da 02 00 f8 12 4 char [] Niz. vrijednost [D, a, t, a,, S, t, r, u, c, t, u, r, e, s] 16 4 int String.hash 0 20 4 (gubitak zbog sljedećeg poravnanja objekta) Instance veličina: 24 bajta Prostorni gubici: 0 bajta unutarnji + 4 bajta vanjski = ukupno 4 bajta

Plitka veličina ovoga Niz Primjer je 24 bajta, koji uključuju 4 bajta predmemoriranog hash koda, 4 bajta od char [] referenca i drugi tipični opći objekt.

Da biste vidjeli stvarnu veličinu char [], možemo raščlaniti i njegov raspored klase:

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

Izgled char [] izgleda ovako:

[C interni elementi objekta: OFFSET SIZE TYPE OPIS VRIJEDNOST 0 4 (zaglavlje objekta) 01 00 00 00 4 4 (zaglavlje objekta) 00 00 00 00 8 4 (zaglavlje objekta) 41 00 00 f8 12 4 (zaglavlje objekta) 0f 00 00 00 16 30 char [C. N / A 46 2 (gubitak zbog sljedećeg poravnanja objekta) Veličina instance: 48 bajta Prostorni gubici: 0 bajta unutarnje + 2 bajta vanjske = 2 bajta

Dakle, imamo 16 bajtova za Tečaj primjerice, 24 bajta za Niz instanci i na kraju 48 bajtova za char []. Ukupno, duboka veličina toga Tečaj instanca je 88 bajtova.

Uvođenjem kompaktnih žica u Javi 9, Niz razred interno koristi a bajt[] za pohranu likova:

java.lang.String unutarnji dijelovi objekta: OFFSET SIZE TYPE OPIS 0 4 (zaglavlje objekta) 4 4 (zaglavlje objekta) 8 4 (zaglavlje objekta) 12 4 byte [] String.value # niz bajtova 16 4 int String.hash 20 1 byte String.coder # encodig 21 3 (gubitak zbog sljedećeg poravnanja objekta)

Stoga je na Javi 9+ ukupan otisak Tečaj instanca bit će 72 bajta umjesto 88 bajtova.

5.6. Izgled grafa objekta

Umjesto da zasebno raščlanimo raspored klasa svakog objekta u grafu predmeta, možemo koristiti GraphLayout. S GraphLayot, samo prođemo početnu točku grafa objekata i on će izvijestiti o rasporedu svih dostupnih objekata s te početne točke. Na taj način možemo izračunati dubinu početne točke grafikona.

Na primjer, možemo vidjeti ukupan otisak Tečaj primjer kako slijedi:

System.out.println (GraphLayout.parseInstance (tečaj) .toFootprint ());

Koji ispisuje sljedeći sažetak:

[adresa zaštićena e-poštom] Otisak: BROJ PROSJEČNI OPIS ZBORA 1 48 48 [C 1 16 16 com.baeldung.objectsize.Kurs 1 24 24 java.lang.String 3 88 (ukupno)

To je ukupno 88 bajtova. The totalSize () metoda vraća ukupni otisak objekta, koji iznosi 88 bajtova:

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

6. Instrumentacija

Da bismo izračunali plitku veličinu objekta, također možemo koristiti Java instrumentacijski paket i Java agente. Prvo, trebali bismo stvoriti razred s a premain () metoda:

javna klasa ObjectSizeCalculator {privatna statička instrumentacija; javna statička praznina premain (String args, Instrumentation inst) {instrumentation = inst; } javna statička long sizeOf (Objekt o) {return instrumentation.getObjectSize (o); }}

Kao što je gore prikazano, koristit ćemo getObjectSize () metoda za pronalaženje plitke veličine predmeta. Također nam treba datoteka manifesta:

Premain-klasa: com.baeldung.objectsize.ObjectSizeCalculator

Zatim upotrijebite ovo NAJVIŠE.MF datoteku, možemo stvoriti JAR datoteku i koristiti je kao Java agent:

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

Napokon, ako pokrenemo bilo koji kod s -javaagent: /path/to/agent.jar argument, onda možemo koristiti veličina() metoda:

Niz ds = "Strukture podataka"; Tečaj tečaja = novi tečaj (ds); System.out.println (ObjectSizeCalculator.sizeOf (naravno));

Ovo će ispisati 16 kao plitku veličinu Tečaj primjer.

7. Statistika klase

Da bismo vidjeli plitku veličinu objekata u već pokrenutoj aplikaciji, možemo pogledati statistiku razreda pomoću jcmd:

$ jcmd GC.class_stats [output_columns]

Na primjer, možemo vidjeti veličinu i broj svake instance Tečaj primjerci:

$ jcmd 63984 GC.class_stats InstSize, InstCount, InstBytes | grep Tečaj 63984: InstSize InstCount InstBytes Naziv klase 16 1 16 com.baeldung.objectsize.Course

Opet, ovo izvještava o plitkoj veličini svake od njih Tečaj primjerice kao 16 bajtova.

Da bismo vidjeli statistiku razreda, trebali bismo pokrenuti aplikaciju s -XX: + OtključajDijagnostičkeVMOpcije zastava za podešavanje.

8. Odlagalište hrpe

Korištenje odlagališta hrpe je još jedna opcija za provjeru veličina instance u pokrenutim aplikacijama. Na taj način možemo vidjeti zadržanu veličinu za svaku instancu. Za uklanjanje hrpe možemo koristiti jcmd kako slijedi:

$ jcmd GC.heap_dump [opcije] / put / do / dump / datoteka

Na primjer:

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

Ovo će stvoriti odlagalište hrpe na navedenom mjestu. Također, s -svi opcija, svi dostupni i nedostupni objekti bit će prisutni na odlagalištu hrpe. Bez ove opcije, JVM će izvesti puni GC prije stvaranja odlagališta hrpe.

Nakon što dobijemo dump heap-a, možemo ga uvesti u alate poput Visual VM-a:

Kao što je gore prikazano, zadržana je veličina jedinog Tečaj instanca je 24 bajta. Kao što je ranije spomenuto, zadržana veličina može biti bilo gdje između plitkih (16 bajtova) i dubokih veličina (88 bajtova).

Također je vrijedno spomenuti da je Visual VM bio dio distribucije Oracle i Open JDK prije Jave 9. Međutim, to više nije slučaj kao Java 9, a Visual VM trebali bismo zasebno preuzeti s njegove web stranice.

9. Zaključak

U ovom uputstvu upoznali smo se s različitim mjernim podacima za mjerenje veličina objekata u JVM vremenu izvođenja. Nakon toga, zapravo smo mjerili veličine primjeraka pomoću različitih alata kao što su JOL, Java Agents i jcmd uslužni program naredbenog retka.

Kao i obično, svi su primjeri dostupni na GitHubu.