Praćenje izvorne memorije u JVM-u

1. Pregled

Jeste li se ikad pitali zašto Java programi troše mnogo više memorije od navedene količine putem dobro poznatog -Xms i -Xmx ugađanje zastava? Iz različitih razloga i mogućih optimizacija, JVM može dodijeliti dodatnu matičnu memoriju. Ova dodatna izdvajanja mogu na kraju povećati potrošnju memorije izvan -Xmx ograničenje.

U ovom uputstvu nabrojit ćemo nekoliko uobičajenih izvora dodjeljivanja nativne memorije u JVM-u, zajedno s njihovim zastavicama za podešavanje veličine, a zatim naučiti kako koristiti Praćenje izvorne memorije da ih nadgleda.

2. Izvorne dodjele

Hrpa je obično najveći potrošač memorije u Java programima, ali postoje i drugi. Osim hrpe, JVM izdvaja prilično velik dio iz matične memorije za održavanje metapodataka svoje klase, aplikacijskog koda, koda generiranog od strane JIT-a, internih struktura podataka itd. U sljedećim odjeljcima istražit ćemo neka od tih izdvajanja.

2.1. Metaprostor

Da bi zadržao neke metapodatke o učitanim klasama, JVM koristi namjensko područje koje se ne naziva hrpom Metaprostor. Prije Jave 8 zvao se ekvivalent PermGen ili Trajna generacija. Metaspace ili PermGen sadrže metapodatke o učitanim klasama, a ne o njihovim primjerima, koji se čuvaju u hrpi.

Ovdje je najvažnije to konfiguracije veličine hrpe neće utjecati na veličinu Metaspacea budući da je Metaspace područje podataka bez gomile. Da bismo ograničili veličinu Metaspacea, koristimo druge zastavice za podešavanje:

  • -XX: MetaspaceSize i -XX: MaxMetaspaceSize za postavljanje minimalne i maksimalne veličine metaprostora
  • Prije Jave 8, -XX: PermSize i -XX: MaxPermSize za postavljanje minimalne i maksimalne veličine PermGen-a

2.2. Niti

Jedno od područja podataka koje JVM-u najviše oduzimaju memoriju je stog stvoren istodobno sa svakom niti. Stog pohranjuje lokalne varijable i djelomične rezultate, igrajući važnu ulogu u pozivima metode.

Zadana veličina stoga niti ovisi o platformi, ali u većini modernih 64-bitnih operativnih sustava iznosi oko 1 MB. Ova se veličina može konfigurirati putem -Xss zastava za podešavanje.

Za razliku od ostalih podatkovnih područja, ukupna memorija dodijeljena hrpama praktički je neograničena kada nema ograničenja na broj niti. Također je vrijedno spomenuti da samom JVM-u treba nekoliko niti za obavljanje svojih internih operacija poput GC-a ili pravovremenih kompilacija.

2.3. Predmemorija koda

Da bi se JVM bajt kod pokrenuo na različitim platformama, treba ga pretvoriti u strojne upute. JIT kompajler odgovoran je za ovu kompilaciju dok se program izvršava.

Kada JVM kompajlira bajtkod u upute za montažu, on ih pohranjuje u posebno nazvano područje podataka bez hrpe Predmemorija koda. Predmemorijom koda može se upravljati kao i ostalim područjima podataka u JVM-u. The -XX: InitialCodeCacheSize i -XX: ReservedCodeCacheSize zastavice za podešavanje određuju početnu i najveću moguću veličinu predmemorije koda.

2.4. Kolekcija smeća

JVM se isporučuje s pregršt GC algoritama, svaki pogodan za različite slučajeve upotrebe. Svi ti GC algoritmi dijele jednu zajedničku crtu: za obavljanje svojih zadataka trebaju koristiti neke podatkovne strukture izvan hrpe. Te unutarnje strukture podataka troše više nativne memorije.

2.5. Simboli

Krenimo od Žice, jedan od najčešće korištenih tipova podataka u aplikacijskom i knjižničnom kodu. Zbog svoje sveprisutnosti obično zauzimaju velik dio Hrpe. Ako velik broj tih nizova sadrži isti sadržaj, tada će značajan dio hrpe biti izgubljen.

Kako bismo uštedjeli malo hrpe prostora, možemo pohraniti po jednu verziju svake Niz i neka drugi upućuju na pohranjenu verziju. Taj se proces naziva String interniranje.Budući da JVM može samo pripravnik Sastavite konstante vremenskih nizova, možemo ručno nazvati pripravnik () metoda na žicama koje namjeravamo internirati.

JVM pohranjuje internirane žice u poseban izvorni hashtable fiksne veličine pod nazivom String stol, također poznat kao Gudački bazen. Veličinu tablice (tj. Broj segmenata) možemo konfigurirati putem -XX: StringTableSize zastava za podešavanje.

Pored tablice nizova, postoji još jedno nativno područje podataka koje se naziva Runtime Constant Pool. JVM koristi ovo spremište za pohranu konstanti poput numeričkih literala za vrijeme prevođenja ili referenci metoda i polja koje se moraju riješiti tijekom izvođenja.

2.6. Nativni bajtovi me uspremnici

JVM je uobičajeno sumnjiv za značajan broj nativnih dodjela, ali ponekad programeri mogu i izravno dodijeliti nativnu memoriju. Najčešći pristupi su malloc poziv izravno iz JNI-ja i NIO-a ByteBuffers.

2.7. Dodatne zastavice za podešavanje

U ovom smo odjeljku koristili pregršt zastavica za podešavanje JVM-a za različite scenarije optimizacije. Pomoću sljedećeg savjeta možemo pronaći gotovo sve zastavice za podešavanje povezane s određenim konceptom:

$ java -XX: + PrintFlagsFinal -verzija | grep 

The PrintFlagsFinal ispisuje sve -XX mogućnosti u JVM-u. Na primjer, da biste pronašli sve zastavice povezane s Metaspaceom:

$ java -XX: + PrintFlagsFinal -verzija | grep Metaspace // skraćeni uintx MaxMetaspaceSize = 18446744073709547520 {product} uintx MetaspaceSize = 21807104 {pd product} // skraćeni

3. Izvorno praćenje memorije (NMT)

Sad kad znamo uobičajene izvore dodjeljivanja nativne memorije u JVM-u, vrijeme je da otkrijemo kako ih nadzirati. Prvo, trebali bismo omogućiti praćenje matične memorije koristeći još jednu zastavicu za podešavanje JVM-a: -XX: NativeMemoryTracking = isključeno | sažeti | detalj. Prema zadanim postavkama NMT je isključen, ali možemo mu omogućiti da vidi sažetak ili detaljan prikaz svojih zapažanja.

Pretpostavimo da želimo pratiti izvorne dodjele za tipičnu aplikaciju Spring Boot:

$ java -XX: NativeMemoryTracking = sažetak -Xms300m -Xmx300m -XX: + UseG1GC -jar app.jar

Ovdje omogućujemo NMT dok dodijeljujemo 300 MB prostora hrpe, s G1 kao našim GC algoritmom.

3.1. Instant snimke

Kad je omogućen NMT, podatke o matičnoj memoriji možemo dobiti u bilo kojem trenutku pomoću jcmd naredba:

$ jcmd VM.native_memory

Da bismo pronašli PID za JVM aplikaciju, možemo koristiti jpsnaredba:

$ jps -l 7858 app.jar // Ovo je naša aplikacija 7899 sun.tools.jps.Jps

Sad ako koristimo jcmd s odgovarajućim pid, VM.native_memory čini da JVM ispisuje informacije o izvornim dodjelama:

$ jcmd 7858 VM.native_memory

Analizirajmo NMT izlazne odjeljke po odjeljcima.

3.2. Ukupna izdvajanja

NMT izvještava o ukupnoj rezerviranoj i predanoj memoriji kako slijedi:

Praćenje izvorne memorije: Ukupno: rezervirano = 1731124KB, predano = 448152KB

Rezervirana memorija predstavlja ukupnu količinu memorije koju naša aplikacija potencijalno može koristiti. Suprotno tome, posvećena memorija jednaka je količini memorije koju naša aplikacija trenutno koristi.

Unatoč dodjeli 300 MB hrpe, ukupna rezervirana memorija za našu aplikaciju je gotovo 1,7 GB, puno više od toga. Slično tome, posvećena memorija iznosi oko 440 MB, što je opet puno više od tih 300 MB.

Nakon ukupnog odjeljka, NMT izvještava o dodjeli memorije po izvoru dodjele. Dakle, istražimo dubinski svaki izvor.

3.3. Hrpa

NMT izvještava o dodjeli hrpe kako smo očekivali:

Java hrpa (rezervirano = 307200KB, predano = 307200KB) (mmap: rezervirano = 307200KB, predano = 307200KB)

300 MB rezervirane i namjenske memorije, što odgovara našim postavkama veličine hrpe.

3.4. Metaprostor

Evo što NMT kaže o metapodacima klase za učitane klase:

Klasa (rezervirano = 1091407KB, predano = 45815KB) (klase # 6566) (malloc = 10063KB # 8519) (mmap: rezervirano = 1081344KB, predano = 35752KB)

Gotovo 1 GB rezervirano i 45 MB predano za učitavanje 6566 klasa.

3.5. Nit

A ovdje je i NMT izvješće o dodjeli niti:

Tema (rezervirano = 37018 KB, predana = 37018 KB) (nit # 37) (stog: rezervirano = 36864 KB, predana = 36864 KB) (malloc = 112 KB # 190) (arena = 42 KB # 72)

Ukupno je 36 MB memorije dodijeljeno snopovima za 37 niti - gotovo 1 MB po snopu. JVM dodjeljuje memoriju nitima u vrijeme izrade, tako da su rezervirana i predana izdvajanja jednaka.

3.6. Predmemorija koda

Pogledajmo što NMT kaže o generiranim i predmemoriranim uputama za montažu JIT-a:

Kôd (rezervirano = 251549KB, predano = 14169KB) (malloc = 1949KB # 3424) (mmap: rezervirano = 249600KB, predano = 12220KB)

Trenutno se gotovo 13 MB koda predmemorira, a taj iznos potencijalno može doseći približno 245 MB.

3.7. GC

Evo izvještaja NMT-a o korištenju memorije G1 GC:

GC (rezervirano = 61771KB, predano = 61771KB) (malloc = 17603KB # 4501) (mmap: rezervirano = 44168KB, predano = 44168KB)

Kao što vidimo, gotovo 60 MB rezervirano je i posvećeno pomaganju G1.

Pogledajmo kako izgleda upotreba memorije za puno jednostavniji GC, recimo Serial GC:

$ java -XX: NativeMemoryTracking = sažetak -Xms300m -Xmx300m -XX: + UseSerialGC -jar app.jar

Serijski GC jedva koristi 1 MB:

GC (rezervirano = 1034 KB, posvećeno = 1034 KB) (malloc = 26 KB # 158) (mmap: rezervirano = 1008 KB, dodijeljeno = 1008 KB)

Očito je da ne bismo trebali odabrati GC algoritam samo zbog njegove upotrebe memorije, jer zaustavljanje prirode serijskog GC-a može prouzročiti pogoršanje performansi. Međutim, postoji nekoliko GC-a koji mogu izabrati i svaki različito uravnotežuje memoriju i performanse.

3.8. Simbol

Evo izvješća NMT o dodjeli simbola, poput tablice nizova i konstantnog spremišta:

Simbol (rezervirano = 10148 KB, posvećeno = 10148 KB) (malloc = 7295 KB # 66194) (arena = 2853 KB # 1)

Gotovo 10 MB dodijeljeno je simbolima.

3.9. NMT s vremenom

NMT nam omogućuje da pratimo kako se dodjela memorije mijenja s vremenom. Prvo, trenutno stanje naše prijave trebali bismo označiti kao polaznu crtu:

$ jcmd VM.native_memory osnovno polazište Osnovno je uspjelo

Zatim, nakon nekog vremena, možemo usporediti trenutnu upotrebu memorije s tom osnovnom linijom:

$ jcmd VM.native_memory summary.diff

NMT, koristeći znakove + i -, rekao bi nam kako se mijenjala upotreba memorije u tom razdoblju:

Ukupno: rezervirano = 1771487KB + 3373KB, predano = 491491KB + 6873KB - Java Heap (rezervirano = 307200KB, predano = 307200KB) (mmap: rezervirano = 307200KB, predano = 307200KB) - Klasa (rezervirano = 1084300KB + 2103KB, predano = 39356KB + 2871KB ) // skraćeno

Ukupna rezervirana i predana memorija povećala se za 3 MB, odnosno 6 MB. Ostale fluktuacije u dodjeli memorije mogu se uočiti jednako lako.

3.10. Detaljni NMT

NMT može pružiti vrlo detaljne informacije o mapi cijelog prostora memorije. Da bismo omogućili ovo detaljno izvješće, trebali bismo koristiti -XX: NativeMemoryTracking = detalj zastava za podešavanje.

4. Zaključak

U ovom smo članku nabrojali različite suradnike u dodjeli nativne memorije u JVM-u. Zatim smo naučili kako pregledati pokrenutu aplikaciju radi praćenja njezinih izvornih dodjela. Pomoću ovih uvida možemo učinkovitije prilagoditi svoje aplikacije i prilagoditi svoje vrijeme izvođenja.


$config[zx-auto] not found$config[zx-overlay] not found