Opsežno sakupljanje smeća na Javi

1. Pregled

U ovom vodiču, pogledati ćemo kako uključiti opsežno prikupljanje smeća u Java aplikaciji. Započet ćemo s predstavljanjem što je detaljno prikupljanje smeća i zašto ono može biti korisno.

Zatim ćemo pogledati nekoliko različitih primjera i naučit ćemo o različitim dostupnim opcijama konfiguracije. Dodatno, Također ćemo se usredotočiti na to kako protumačiti izlaz naših detaljnih dnevnika.

Da biste saznali više o skupljanju smeća (GC) i različitim dostupnim implementacijama, pogledajte naš članak o Javanskim skupljačima smeća.

2. Kratki uvod u detaljno prikupljanje smeća

Uključivanje detaljnog evidentiranja odvoza smeća često je potrebno prilikom podešavanja i uklanjanja pogrešaka mnogih problema, posebno problemi s memorijom. U stvari, neki bi tvrdili da bismo, kako bismo strogo nadzirali zdravlje naše aplikacije, uvijek trebali nadzirati izvedbu JVM-ovog odvoza smeća.

Kao što ćemo vidjeti, GC zapisnik je vrlo važan alat za otkrivanje potencijalnih poboljšanja hrpe i GC konfiguracije naše aplikacije. Za svaki GC događaj, GC zapisnik pruža točne podatke o njegovim rezultatima i trajanju.

Vremenom nam analiza ovih podataka može pomoći da bolje razumijemo ponašanje naše aplikacije i prilagoditi izvedbu naše aplikacije. Štoviše, može pomoći u optimizaciji GC frekvencije i vremena prikupljanja određivanjem najboljih veličina hrpe, ostalih JVM opcija i zamjenskih GC algoritama.

2.1. Jednostavan Java program

Upotrijebit ćemo izravni Java program da pokažemo kako omogućiti i protumačiti naše GC zapisnike:

javna klasa Application {private static Map stringContainer = new HashMap (); javna statička void glavna (String [] args) {System.out.println ("Start programa!"); Niz stringWithPrefix = "stringWithPrefix"; // Učitajte Java Heap s 3 M instance java.lang.String za (int i = 0; i <3000000; i ++) {String newString = stringWithPrefix + i; stringContainer.put (newString, newString); } System.out.println ("Veličina MAPE:" + stringContainer.size ()); // Eksplicitni GC! System.gc (); // Ukloni 2 M od 3 M za (int i = 0; i <2000000; i ++) {String newString = stringWithPrefix + i; stringContainer.remove (newString); } System.out.println ("Veličina MAPE:" + stringContainer.size ()); System.out.println ("Kraj programa!"); }}

Kao što možemo vidjeti u gornjem primjeru, ovaj jednostavni program učitava 3 milijuna Niz instance u a Karta objekt. Zatim upućujemo eksplicitni poziv sakupljaču smeća pomoću System.gc ().

Napokon uklanjamo 2 milijuna Niz primjeri iz Karta. Također izričito koristimo System.out.println kako bi se olakšalo tumačenje rezultata.

U sljedećem ćemo odjeljku vidjeti kako aktivirati zapisivanje GC-a.

3. Aktiviranje "jednostavnog" bilježenja GC-a

Počnimo s pokretanjem našeg programa i omogućavanjem detaljnog GC-a putem naših JVM-ovih startnih argumenata:

-XX: + UseSerialGC -Xms1024m -Xmx1024m -verbose: gc

Ovdje je važan argument -verbozna: gc, koji aktivira evidentiranje podataka o odvozu smeća u najjednostavnijem obliku. Prema zadanim postavkama zapisuje se u GC zapisnik stdout i trebao bi iznijeti redak za svaku mladu generaciju GC i svaki puni GC.

Za potrebe našeg primjera, argumentom smo naveli serijski sakupljač smeća, najjednostavniju GC implementaciju -XX: + UseSerialGC.

Također smo postavili minimalnu i maksimalnu veličinu hrpe od 1024mb, ali naravno, postoji još JVM parametara koje možemo prilagoditi.

3.1. Osnovno razumijevanje opširnog rezultata

Sada pogledajmo rezultate našeg jednostavnog programa:

Početak programa! [GC (neuspjeh dodjele) 279616K-> 146232K (1013632K), 0,3318607 s] [GC (neuspjeh dodjele) 425848K-> 295442K (1013632K), 0,4266943 s] MAP veličina: 3000000 [Cijeli GC (System.gc-)) 434341K > 368279K (1013632K), 0,5420611 sek]] [GC (neuspjeh dodjele) 647895K-> 368280K (1013632K), 0,0075449 sek] Veličina MAPE: 1000000 Kraj programa!

U gornjem izlazu već možemo vidjeti puno korisnih informacija o tome što se događa unutar JVM-a.

U početku ovaj izlaz može izgledati prilično zastrašujuće, ali krenimo sada korak po korak.

Kao prvo, možemo vidjeti da su se dogodile četiri zbirke, jedna Full GC i tri čišćenja Mlade generacije.

3.2. Opširniji ishod detaljnije

Razložimo izlazne linije detaljnije da bismo točno razumjeli što se događa:

  1. GC ili Puni GCBilo koja vrsta odvoza smeća GC ili Puni GC razlikovati manje ili potpuno odvoz smeća
  2. (Neuspjeh u dodjeli) ili (System.gc ()) - uzrok sakupljanja - Neuspjeh u dodjeli znači da u Edenu nije ostalo više prostora za raspoređivanje naših predmeta
  3. 279616K-> 146232K - zauzeta memorija hrpe prije i nakon GC-a (odvojena strelicom)
  4. (1013632K) - Trenutni kapacitet hrpe
  5. 0,3318607 sek - Trajanje GC događaja u sekundama

Dakle, ako uzmemo prvi redak, 279616K-> 146232K (1013632K) znači da je GC smanjio zauzetu hrpu memorije sa 279616K do 146232K. Kapacitet hrpe u vrijeme GC bio je 1013632K, a GC je uzeo 0.3318607 sekunde.

Međutim, iako jednostavan format zapisivanja GC-a može biti koristan, pruža ograničene detalje. Na primjer, ne možemo reći je li GC premještao neke predmete iz mlade u staru generaciju ili koja je bila ukupna veličina mlade generacije prije i nakon svake zbirke.

Iz tog razloga detaljno bilježenje GC-a korisnije je od jednostavnog.

4. Aktiviranje "detaljnog" bilježenja GC-a

Da bismo aktivirali detaljno bilježenje GC-a, koristimo argument -XX: + IspisGCDetalji. To će nam dati više detalja o svakom GC-u, kao što su:

  • Veličina mlade i stare generacije prije i nakon svakog GC
  • Vrijeme koje je potrebno da se GC dogodi u mladoj i starijoj generaciji
  • Veličina predmeta koji se promoviraju na svakom GC-u
  • Sažetak veličine ukupne hrpe

U sljedećem ćemo primjeru vidjeti kako u naše dnevnike kombinirati još detaljnije informacije -verbozna: gc s ovim dodatnim argumentom.

Imajte na umu da -XX: + IspisGCDetalji zastara je zastarjela u Javi 9, u korist novog objedinjenog mehanizma zapisivanja (više o tome kasnije). U svakom slučaju, novi ekvivalent -XX: + IspisGCDetalji je -Xlog: gc * opcija.

5. Tumačenje "detaljnog" detaljnog rezultata

Ponovno pokrenimo naš primjer programa:

-XX: + UseSerialGC -Xms1024m -Xmx1024m -verbose: gc -XX: + IspisGCDetails

Ovaj put je izlaz prilično opširniji:

Početak programa! [GC (neuspjeh dodjele) [DefNew: 279616K-> 34944K (314560K), 0,3626923 secs] 279616K-> 146232K (1013632K), 0,3627492 secs] [Times: user = 0,33 sys = 0,03, real = 0,36 secs] [GC (Dodjela) Neuspjeh) [DefNew: 314560K-> 34943K (314560K), 0.4589079 secs] 425848K-> 295442K (1013632K), 0.4589526 secs] [Times: user = 0.41 sys = 0.05, real = 0.46 secs] Veličina MAPE: 3000000 [Cijeli GC ( System.gc ()) [Tenured: 260498K-> 368281K (699072K), 0.5580183 secs] 434341K-> 368281K (1013632K), [Metaspace: 2624K-> 2624K (1056768K)], 0.5580738 secs] [Times: user = 0,50 sys = 0,06, stvarno = 0,56 sek] [GC (neuspjeh dodjele) [DefNew: 279616K-> 0K (314560K), 0,0076722 sek] 647897K-> 368281K (1013632K), 0,0077169 sek] = 0,01 sek] Veličina MAPE: 1000000 Kraj programa! Heap def nova generacija ukupno 314560K, iskorišteno 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden prostor 279616K, 35% iskorišteno [0x00000000c0000000, 0x00000000c61e9370, 0x00000000d1110000) iz prostora 34944K, 0% rabljeno, 000000000000000000000000000000000000000 rabljeno [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) ukupno generirano 699072K, uporabljeno 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) prostor 699072K, 52% uporabljeno [0x00000000d5550000, 0x00000000e00000000000000000000000000000000000000 prostor klase korišten 283K, kapacitet 386K, predano 512K, rezervirano 1048576K

Morali bismo biti u stanju prepoznati sve elemente iz jednostavnog GC dnevnika. Ali postoji nekoliko novih predmeta.

Razmotrimo sada nove stavke u izlazu koje su u sljedećem odjeljku označene plavom bojom:

5.1. Tumačenje manjeg GC-a u mladoj generaciji

Započet ćemo analizom novih dijelova u manjem GC:

  • [GC (neuspjeh dodjele) [DefNew: 279616K-> 34944K (314560K), 0.3626923 secs] 279616K-> 146232K (1013632K), 0.3627492 secs] [Times: user = 0.33 sys = 0.03, real = 0.36 secs]

Kao i prije, razdvojit ćemo linije na dijelove:

  1. DefNew - Naziv korištenog sakupljača smeća. Ovaj ne tako očit naziv označava sakupljač smeća s jednim navojem i kopira oznaku copy-the-world i ono je što se koristi za čišćenje mlade generacije
  2. 279616K-> 34944K - Korištenje mlade generacije prije i poslije sakupljanja
  3. (314560K) - Ukupna veličina mlade generacije
  4. 0,3626923 s - Trajanje u sekundama
  5. [Vrijeme: korisnik = 0,33 sys = 0,03, stvarno = 0,36 sek] - Trajanje GC događaja, mjereno u različitim kategorijama

Sada objasnimo različite kategorije:

  • korisnik - Ukupno CPU vrijeme koje je potrošio Garbage Collector
  • sys - Vrijeme provedeno u OS pozivima ili čekanju sistemskih događaja
  • stvaran - Ovo je sve proteklo vrijeme, uključujući vremenske odsječke koje koriste drugi procesi

Budući da svoj primjer pokrećemo pomoću serijskog sakupljača smeća, koji uvijek koristi samo jednu nit, u stvarnom vremenu jednak je zbroju korisničkih i sistemskih vremena.

5.2. Tumačenje cjelovitog GC

U ovom pretposljednjem primjeru vidimo da je za glavnu kolekciju (Full GC), koju je pokrenuo naš sistemski poziv, korišteni kolektor Određen.

Posljednji dodatak koji vidimo je raščlamba koja slijedi isti obrazac za Metaprostor:

[Metaprostor: 2624K-> 2624K (1056768K)], 0,5580738 sek]

Metaprostor je novi memorijski prostor uveden u Javi 8 i područje je izvorne memorije.

5.3. Analiza raščlambe hrpe Java

Završni dio rezultata uključuje raščlambu hrpe uključujući sažetak memorijskog otiska za svaki dio memorije.

Možemo vidjeti da je prostor Edena imao 35% otiska, a Tenured 52% otiska. Sadržan je i sažetak za prostor metapodataka i prostor klasa.

Iz gornjih primjera, sada možemo točno razumjeti što se događalo s potrošnjom memorije u JVM-u tijekom GC događaja.

6. Dodavanje podataka o datumu i vremenu

Nijedan dobar zapis nije potpun bez podataka o datumu i vremenu.

Ove dodatne informacije mogu biti vrlo korisne kada moramo povezati podatke dnevnika GC s podacima iz drugih izvora ili jednostavno mogu olakšati pretraživanje.

Sljedeća dva argumenta možemo dodati kada pokrenemo našu aplikaciju kako bismo dobili podatke o datumu i vremenu koji će se pojaviti u našim zapisnicima:

-XX: + PrintGCTimeStamps -XX: + PrintGCDateStamps

Svaki redak sada započinje apsolutnim datumom i vremenom kada je napisan, a slijedi vremenska oznaka koja odražava realno vrijeme koje je prošlo u sekundama od početka JVM-a:

2018-12-11T02: 55: 23.518 + 0100: 2.601: [GC (Dodjela ...

Imajte na umu da su ove zastavice za podešavanje uklonjene u Javi 9. Nova alternativa je:

-Xlog: gc * :: vrijeme

7. Prijava u datoteku

Kao što smo već vidjeli, prema zadanim postavkama zapisuje se u GC zapisnik stdout. Praktičnije rješenje je navođenje izlazne datoteke.

To možemo učiniti pomoću argumenta -Xloggc: gdje datoteka je apsolutni put do naše izlazne datoteke:

-Xloggc: /path/to/file/gc.log

Slično ostalim zastavicama za podešavanje, Java 9 zastarjela je zastavicu -Xloggc u korist novog objedinjenog zapisivanja. Da budemo precizniji, sada je alternativa prijavljivanju u datoteku:

-Xlog: gc: /path/to/file/gc.log

8. Java 9: ​​Objedinjeno JVM bilježenje

Od Jave 9, većina zastavica za podešavanje povezanih s GC-om zastarjela je u korist objedinjene opcije bilježenja -Xlog: gc. The opširno: gc opcija, međutim, i dalje radi u Javi 9 i novijoj verziji.

Na primjer, od Jave 9, ekvivalent -verbozna: gc zastava u novom jedinstvenom sustavu zapisivanja je:

-Xlog: gc

Ovo će prijaviti sve zapise GC razine informacija na standardni izlaz. Također je moguće koristiti -Xlog: gc = sintaksa za promjenu razine dnevnika. Na primjer, da biste vidjeli sve zapisnike na razini otklanjanja pogrešaka:

-Xlog: gc = ispravljanje pogrešaka

Kao što smo vidjeli ranije, izlazno odredište možemo promijeniti putem -Xlog: gc =: sintaksa. Prema zadanim postavkama izlaz je stdout, ali možemo ga promijeniti u stderr ili čak datoteka:

-Xlog: gc = otklanjanje pogrešaka: datoteka = gc.txt

Također je moguće dodati još nekoliko polja na izlaz pomoću dekoratera. Na primjer:

-Xlog: gc = debug :: pid, vrijeme, vrijeme rada

Ovdje ispisujemo ID procesa, vrijeme rada i trenutnu vremensku oznaku u svakom izrazu dnevnika.

Da biste vidjeli više primjera jedinstvenog JVM zapisivanja, pogledajte standard JEP 158.

9. A Alat za analizu GC dnevnika

Analiziranje GC dnevnika pomoću uređivača teksta može biti dugotrajno i prilično zamorno. Ovisno o verziji JVM i GC algoritmu koji se koristi, format GC dnevnika može se razlikovati.

Postoji vrlo dobar besplatni alat za grafičku analizu koji analizira zapisnike sakupljanja smeća, pruža brojne mjerne podatke o potencijalnim problemima odvoza smeća, pa čak nudi i potencijalna rješenja za te probleme.

Svakako pogledajte Universal GC Log Analyzer!

10. Zaključak

Da rezimiramo, u ovom uputstvu detaljno smo istražili detaljno prikupljanje smeća na Javi.

Prvo smo započeli s predstavljanjem što je detaljno prikupljanje smeća i zašto bismo ga možda željeli koristiti. Zatim smo pogledali nekoliko primjera pomoću jednostavne Java aplikacije. Počeli smo s omogućavanjem GC zapisovanja u najjednostavnijem obliku prije istraživanja detaljnijih primjera i načina tumačenja rezultata.

Konačno, istražili smo nekoliko dodatnih mogućnosti za bilježenje podataka o vremenu i datumu te kako zapisati podatke u datoteku dnevnika.

Primjeri koda mogu se naći na GitHubu.