Sledovanie natívnej pamäte v JVM

1. Prehľad

Zaujímalo vás, prečo aplikácie Java spotrebúvajú oveľa viac pamäte, ako je stanovené množstvo prostredníctvom toho známeho -Xms a -Xmx ladenie vlajok? Z rôznych dôvodov a možných optimalizácií môže JVM alokovať extra natívnu pamäť. Tieto ďalšie pridelenia môžu nakoniec zvýšiť spotrebovanú pamäť nad rámec -Xmx obmedzenie.

V tomto výučbe uvedieme niekoľko bežných zdrojov alokácie natívnej pamäte v JVM spolu s ich príznakmi ladenia veľkosti a potom sa naučíme používať Natívne sledovanie pamäte ich sledovať.

2. Natívne alokácie

Hromada je zvyčajne najväčším spotrebiteľom pamäte v Java aplikáciách, existujú však aj ďalšie. Okrem hromady JVM vyhradzuje pomerne veľkú časť z natívnej pamäte na zachovanie svojich metadát triedy, kódu aplikácie, kódu generovaného JIT, interných dátových štruktúr atď. V nasledujúcich častiach preskúmame niektoré z týchto pridelení.

2.1. Metaspace

Na udržanie niektorých metaúdajov o načítaných triedach používa server JVM vyhradenú ne haldy nazývanú oblasť Metaspace. Pred jazykom Java 8 sa ekvivalent volal PermGen alebo Stála generácia. Metaspace alebo PermGen obsahuje skôr metaúdaje o načítaných triedach ako o ich inštanciách, ktoré sa uchovávajú v halde.

Tu je dôležité to konfigurácie dimenzie haldy neovplyvnia veľkosť metaspace pretože Metaspace je dátová oblasť mimo haldy. Aby sme obmedzili veľkosť Metaspace, používame ďalšie ladiace príznaky:

  • -XX: MetaspaceSize a -XX: MaxMetaspaceSize na nastavenie minimálnej a maximálnej veľkosti metaspace
  • Pred programom Java 8, -XX: PermSize a -XX: MaxPermSize na nastavenie minimálnej a maximálnej veľkosti PermGen

2.2. Nite

Jednou z pamäťovo najnáročnejších dátových oblastí v JVM je zásobník, ktorý sa vytvára súčasne s každým vláknom. Zásobník ukladá miestne premenné a čiastočné výsledky, pričom hrá dôležitú úlohu pri vyvolaní metód.

Predvolená veľkosť zásobníka vlákien je závislá od platformy, ale vo väčšine moderných 64-bitových operačných systémov je to okolo 1 MB. Túto veľkosť je možné konfigurovať pomocou -Xss ladiaca vlajka.

Na rozdiel od iných dátových oblastí celková pamäť pridelená zásobníkom je prakticky neobmedzená, ak neexistuje žiadne obmedzenie počtu vlákien. Je tiež potrebné spomenúť, že samotný JVM potrebuje niekoľko vlákien na vykonávanie svojich vnútorných operácií, ako je GC alebo kompilácie just-in-time.

2.3. Cache kódu

Aby bolo možné spustiť bytecode JVM na rôznych platformách, je potrebné ho skonvertovať na strojové pokyny. Za túto kompiláciu je zodpovedný program JIT, keď sa program vykonáva.

Keď JVM kompiluje bytecode podľa pokynov na zostavenie, uloží tieto pokyny do špeciálnej nehumánnej dátovej oblasti s názvom Cache kódu. Cache kódu je možné spravovať rovnako ako iné dátové oblasti v JVM. The -XX: InitialCodeCacheSize a -XX: ReservedCodeCacheSize ladiace príznaky určujú počiatočnú a maximálnu možnú veľkosť medzipamäte kódu.

2.4. Zber odpadu

JVM je dodávaný s niekoľkými GC algoritmami, z ktorých každý je vhodný pre rôzne prípady použitia. Všetky tieto algoritmy GC zdieľajú jednu spoločnú vlastnosť: na vykonávanie svojich úloh musia používať niektoré dátové štruktúry mimo haldy. Tieto interné dátové štruktúry spotrebúvajú viac natívnej pamäte.

2.5. Symboly

Začnime s Struny, jeden z najbežnejšie používaných typov údajov v kóde aplikácie a knižnice. Pre svoju všadeprítomnosť obvykle zaberajú veľkú časť haldy. Ak veľké množstvo týchto reťazcov obsahuje rovnaký obsah, potom dôjde k premrhaniu významnej časti haldy.

Aby sme ušetrili nejaké haldy, môžeme ukladať vždy jednu verziu String a nechať ostatných odkazovať na uloženú verziu. Tento proces sa nazýva String Interning.Pretože JVM môže iba stážovať Zostavte konštanty časového reťazca, môžeme manuálne zavolať stážista () metóda na strunách, ktoré máme v úmysle internovať.

JVM ukladá interné reťazce do špeciálneho natívneho formátu hash s pevnou veľkosťou, ktorý sa nazýva Reťazcový stôl, tiež známy ako String Pool. Môžeme konfigurovať veľkosť tabuľky (t.j. počet segmentov) pomocou -XX: StringTableSize ladiaca vlajka.

Okrem tabuľky reťazcov existuje ešte jedna natívna dátová oblasť s názvom Runtime Constant Pool. JVM používa tento fond na ukladanie konštánt, ako sú číselné literály v čase kompilácie alebo odkazy na metódy a polia, ktoré sa musia vyriešiť za behu programu.

2.6. Natívne bajtové medzipamäte

JVM je obvyklým podozrivým z významného počtu natívnych alokácií, ale niekedy môžu vývojári priamo alokovať aj natívnu pamäť. Najbežnejším prístupom je malloc hovor priamym spojením JNI a NIO ByteBuffers.

2.7. Ďalšie ladiace vlajky

V tejto časti sme použili niekoľko ladiacich príznakov JVM pre rôzne optimalizačné scenáre. Pomocou nasledujúceho tipu nájdeme takmer všetky príznaky ladenia súvisiace s konkrétnym konceptom:

$ java -XX: + PrintFlagsFinal -verzia | grep 

The PrintFlagsFinal vypíše všetky -XX možnosti v JVM. Napríklad na vyhľadanie všetkých príznakov súvisiacich s Metaspace:

$ java -XX: + PrintFlagsFinal -verzia | grep Metaspace // skrátený uintx MaxMetaspaceSize = 18446744073709547520 {product} uintx MetaspaceSize = 21807104 {pd product} // skrátený

3. Natívne sledovanie pamäte (NMT)

Teraz, keď poznáme bežné zdroje alokácií natívnej pamäte v JVM, je čas zistiť, ako ich monitorovať. Najskôr by sme mali povoliť sledovanie natívnej pamäte pomocou ešte jedného príznaku ladenia JVM: -XX: NativeMemoryTracking = vypnuté | súhrnné | detail. NMT je predvolene vypnuté, ale môžeme mu povoliť zobrazenie súhrnného alebo podrobného pohľadu na jeho pozorovania.

Predpokladajme, že chceme sledovať natívne alokácie pre typickú aplikáciu Spring Boot:

$ java -XX: NativeMemoryTracking = súhrn -Xms300m -Xmx300m -XX: + UseG1GC -jar app.jar

Tu povoľujeme NMT a prideľujeme 300 MB haldy priestoru, pričom náš algoritmus GC je G1.

3.1. Okamžité snímky

Keď je NMT povolené, môžeme informácie o natívnej pamäti získať kedykoľvek pomocou jcmd príkaz:

$ jcmd VM.native_memory

Na nájdenie PID pre aplikáciu JVM môžeme použiť jpspríkaz:

$ jps -l 7858 app.jar // Toto je naša aplikácia 7899 sun.tools.jps.Jps

Teraz, ak použijeme jcmd s príslušnými pid, VM.native_memory umožňuje JVM vytlačiť informácie o natívnych alokáciách:

$ jcmd 7858 VM.native_memory

Poďme analyzovať výstup NMT sekciu po sekcii.

3.2. Celkové pridelené prostriedky

NMT hlási celkovú rezervovanú a odovzdanú pamäť nasledovne:

Sledovanie natívnej pamäte: Celkom: rezervované = 1731124 kB, odovzdané = 448152 kB

Rezervovaná pamäť predstavuje celkové množstvo pamäte, ktoré môže naša aplikácia potenciálne využiť. Naopak, odovzdaná pamäť sa rovná množstvu pamäte, ktorú momentálne naša aplikácia využíva.

Napriek prideleniu 300 MB haldy je celková vyhradená pamäť pre našu aplikáciu takmer 1,7 GB, čo je oveľa viac. Podobne je nasadená pamäť okolo 440 MB, čo je opäť oveľa viac ako 300 MB.

Po celkovej časti NMT hlási pridelenie pamäte na jeden zdroj pridelenia. Poďme teda preskúmať každý zdroj do hĺbky.

3.3. Halda

NMT hlási naše alokácie haldy podľa našich očakávaní:

Halda Java (vyhradené = 307200 kB, potvrdené = 307200 kB) (mmap: vyhradené = 307200 kB, potvrdené = 307200 kB)

300 MB vyhradenej aj odovzdanej pamäte, čo zodpovedá nášmu nastaveniu veľkosti haldy.

3.4. Metaspace

Tu hovorí NMT o metadátach triedy pre načítané triedy:

Trieda (vyhradené = 1091407 kB, vyhradené = 45815 kB) (triedy # 6566) (malloc = 10063 kB # 8519) (mmap: rezervované = 1081344 kB, vyhradené = 35752 kB)

Takmer 1 GB vyhradené a 45 MB vyhradené na načítanie 6566 tried.

3.5. Závit

A tu je správa NMT o alokácii vlákien:

Vlákno (vyhradené = 37018 kB, nasadené = 37018 kB) (vlákno # 37) (zásobník: vyhradené = 36864 kB, vyhradené = 36864 kB) (malloc = 112 kB # 190) (aréna = 42 kB # 72)

Celkovo je 36 MB pamäte pridelených zásobníkom pre 37 vlákien - takmer 1 MB na zásobník. JVM prideľuje pamäť vláknam v čase vytvorenia, takže vyhradené a pridelené pridelenia sú rovnaké.

3.6. Cache kódu

Pozrime sa, čo hovorí NMT na vygenerované a uložené inštrukcie na montáž pomocou JIT:

Kód (vyhradené = 251549 kB, vyhradené = 14169 kB) (malloc = 1949 kB # 3424) (mmap: vyhradené = 249600 kB, vyhradené = 12220 kB)

V súčasnosti sa ukladá do medzipamäte takmer 13 MB kódu a toto množstvo sa môže potenciálne vyšplhať až na približne 245 MB.

3.7. GC

Tu je správa NMT o využití pamäte G1 GC:

GC (vyhradené = 61771 kB, potvrdené = 61771 kB) (malloc = 17603 kB # 4501) (mmap: vyhradené = 44168 kB, vyhradené = 44168 kB)

Ako vidíme, takmer 60 MB je vyhradených a odhodlaných pomáhať skupine G1.

Pozrime sa, ako vyzerá využitie pamäte pre oveľa jednoduchšiu GC, napríklad Serial GC:

$ java -XX: NativeMemoryTracking = súhrn -Xms300m -Xmx300m -XX: + UseSerialGC -jar app.jar

Serial GC sotva používa 1 MB:

GC (vyhradené = 1034 kB, vyhradené = 1034 kB) (malloc = 26 kB # 158) (mmap: vyhradené = 1008 kB, vyhradené = 1008 kB)

Je zrejmé, že by sme nemali vyberať algoritmus GC len kvôli jeho použitiu pamäte, pretože sériový GC typu Stop-the-World môže spôsobiť zníženie výkonu. Existuje však niekoľko GC na výber a každý z nich vyvažuje pamäť a výkon inak.

3.8. Symbol

Tu je správa NMT o alokáciách symbolov, ako napríklad tabuľka reťazcov a konštantná skupina:

Symbol (vyhradené = 10148 kB, potvrdené = 10148 kB) (malloc = 7295 kB # 66194) (aréna = 2853 kB # 1)

Takmer 10 MB je pridelených symbolom.

3.9. NMT v priebehu času

NMT nám umožňuje sledovať, ako sa v priebehu času mení alokácia pamäte. Najskôr by sme mali označiť aktuálny stav našej aplikácie ako základnú čiaru:

$ jcmd VM.native_memory základná línia Základná línia bola úspešná

Potom po chvíli môžeme porovnať súčasné využitie pamäte s touto základnou líniou:

$ jcmd VM.native_memory summary.diff

NMT pomocou znakov + a - nám oznámi, ako sa zmenilo využitie pamäte za dané obdobie:

Celkom: vyhradené = 1771487 kB + 3373 kB, nasadené = 491491 kB + 6873 kB - Java halda (vyhradené = 307200 kB, vyhradené = 307200 kB) (mmap: rezervované = 307200 kB, vyhradené = 307200 kB) - trieda (vyhradené = 1084300 kB + 2103 kB, vyhradené = 39356 kB + 2871 kB) ) // Skrátené

Celková vyhradená a potvrdená pamäť sa zvýšila o 3 MB, respektíve 6 MB. Ostatné výkyvy v alokácii pamäte možno zistiť tak ľahko.

3.10. Podrobné NMT

NMT môže poskytnúť veľmi podrobné informácie o mape celého pamäťového priestoru. Na povolenie tejto podrobnej správy by sme mali použiť -XX: NativeMemoryTracking = detail ladiaca vlajka.

4. Záver

V tomto článku sme vymenovali rôznych prispievateľov k alokácii natívnej pamäte v JVM. Potom sme sa naučili, ako skontrolovať spustenú aplikáciu a sledovať jej natívne alokácie. Vďaka týmto poznatkom môžeme efektívnejšie vyladiť naše aplikácie a veľkosť našich runtime prostredí.


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