Verbose Garbage Collection i Java

1. Oversigt

I denne vejledning vi kigger på, hvordan du aktiverer detaljeret affaldssamling i en Java-applikation. Vi begynder med at introducere, hvad detaljeret affaldssamling er, og hvorfor det kan være nyttigt.

Dernæst ser vi på flere forskellige eksempler, og vi lærer om de forskellige tilgængelige konfigurationsindstillinger. Derudover vi vil også fokusere på, hvordan man fortolker output fra vores detaljerede logfiler.

For at lære mere om Garbage Collection (GC) og de forskellige tilgængelige implementeringer, se vores artikel om Java Garbage Collectors.

2. Kort introduktion til uddybende affaldssamling

Det er ofte nødvendigt at tænde for detaljeret skrotindsamlingslogning, når man tuning og debugging af mange problemer, især hukommelsesproblemer. Faktisk vil nogle hævde, at for nøje at overvåge vores applikationssundhed, bør vi altid overvåge JVMs Garbage Collection-ydeevne.

Som vi vil se, er GC-loggen et meget vigtigt værktøj til at afsløre potentielle forbedringer af bunken og GC-konfigurationen af ​​vores applikation. For hver GC-forekomst giver GC-loggen nøjagtige data om dens resultater og varighed.

Over tid kan analyser af disse oplysninger hjælpe os med bedre at forstå vores applikations opførsel og hjælpe os med at indstille vores applikations præstationer. I øvrigt, det kan hjælpe med at optimere GC-frekvens og indsamlingstider ved at specificere de bedste bunkestørrelser, andre JVM-muligheder og alternative GC-algoritmer.

2.1. Et simpelt Java-program

Vi bruger et ligetil Java-program til at demonstrere, hvordan du aktiverer og fortolker vores GC-logfiler:

public class Application {privat statisk Map stringContainer = ny HashMap (); public static void main (String [] args) {System.out.println ("Start af program!"); String stringWithPrefix = "stringWithPrefix"; // Indlæs Java Heap med 3 M java.lang.String-forekomster for (int i = 0; i <3000000; i ++) {String newString = stringWithPrefix + i; stringContainer.put (newString, newString); } System.out.println ("MAP-størrelse:" + stringContainer.size ()); // Eksplicit GC! System.gc (); // Fjern 2 M ud af 3 M for (int i = 0; i <2000000; i ++) {String newString = stringWithPrefix + i; stringContainer.remove (newString); } System.out.println ("MAP-størrelse:" + stringContainer.size ()); System.out.println ("Programmets afslutning!"); }}

Som vi kan se i ovenstående eksempel indlæser dette enkle program 3 millioner Snor tilfælde ind i en Kort objekt. Vi foretager derefter et eksplicit opkald til affaldssamleren ved hjælp af System.gc ().

Endelig fjerner vi 2 millioner af Snor tilfælde fra Kort. Vi bruger også eksplicit System.out.println for at gøre det lettere at fortolke output.

I det næste afsnit vil vi se, hvordan du aktiverer GC-logning.

3. Aktivering af "enkel" GC-logning

Lad os starte med at køre vores program og aktivere den detaljerede GC via vores JVM-opstartsargumenter:

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

Det vigtige argument her er -verbose: gc, som aktiverer logning af information om affaldsindsamling i sin enkleste form. GC-loggen er som standard skrevet til stdout og skal udgive en linje for hver GC-generation af unge og hver fulde GC.

Med henblik på vores eksempel har vi specificeret den serielle affaldssamler, den enkleste GC-implementering, via argumentet -XX: + UseSerialGC.

Vi har også indstillet en minimal og maksimal bunke størrelse på 1024 MB, men der er selvfølgelig flere JVM-parametre, vi kan indstille.

3.1. Grundlæggende forståelse af den detaljerede produktion

Lad os nu se på resultatet af vores enkle program:

Start af programmet! [GC (Allocation Failure) 279616K-> 146232K (1013632K), 0.3318607 secs [GC (Allocation Failure) 425848K-> 295442K (1013632K), 0.4266943 secs) KORTstørrelse: 3000000 [Fuld GC (System.gc ()) 434341K- > 368279K (1013632K), 0.5420611 sek] [GC (Allocation Failure) 647895K-> 368280K (1013632K), 0.0075449 sec] KORTstørrelse: 1000000 Programmets afslutning!

I ovenstående output kan vi allerede se en masse nyttige oplysninger om, hvad der foregår inde i JVM.

Først kan denne output se ret skræmmende ud, men lad os nu gennemgå den trin for trin.

Først og fremmest, vi kan se, at fire samlinger fandt sted, en fuld GC og tre rengøring af unge generationer.

3.2. Den detaljerede output mere detaljeret

Lad os nedbryde outputlinjerne mere detaljeret for at forstå præcis, hvad der foregår:

  1. GC eller Fuld GCDen type affaldssamling, enten GC eller Fuld GC for at skelne mellem en mindre eller fuld affaldssamling
  2. (Tildelingsfejl) eller (System.gc ()) - Årsagen til indsamlingen - Tildelingsfejl indikerer, at der ikke var mere plads i Eden til at fordele vores objekter
  3. 279616K-> 146232K - Den besatte bunkehukommelse henholdsvis før og efter GC (adskilt af en pil)
  4. (1013632K) - Bunens nuværende kapacitet
  5. 0.3318607 sek - Varigheden af ​​GC-begivenheden i sekunder

Så hvis vi tager den første linje, 279616K-> 146232K (1013632K) betyder, at GC reducerede den besatte bunkehukommelse fra 279616K til 146232K. Heapkapaciteten på tidspunktet for GC var 1013632K, og GC tog 0.3318607 sekunder.

Men selvom det enkle GC-logningsformat kan være nyttigt, giver det begrænsede detaljer. For eksempel kan vi ikke fortælle, om GC flyttede nogen objekter fra den unge til den gamle generation, eller hvad der var den samlede generations størrelse før og efter hver samling.

Af den grund er detaljeret GC-logning mere nyttig end den enkle.

4. Aktivering af "detaljeret" GC-logning

For at aktivere den detaljerede GC-logning bruger vi argumentet -XX: + PrintGCDetails. Dette giver os flere detaljer om hver GC, såsom:

  • Størrelsen på den unge og gamle generation før og efter hver GC
  • Den tid, det tager for en GC at ske i ung og gammel generation
  • Størrelsen på objekter, der promoveres ved hver GC
  • Et resumé af størrelsen på den samlede bunke

I det næste eksempel vil vi se, hvordan man fanger endnu mere detaljerede oplysninger i vores logfiler kombineret -verbose: gc med dette ekstra argument.

Bemærk, at -XX: + PrintGCDetails flag er udfaset i Java 9 til fordel for den nye samlede logningsmekanisme (mere om dette senere). Alligevel, den nye ækvivalent af -XX: + PrintGCDetails er -Xlog: gc * mulighed.

5. Fortolkning af den "detaljerede" detaljerede output

Lad os køre vores prøveprogram igen:

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

Denne gang er output noget mere detaljeret:

Start af programmet! [GC (Allocation Failure) [DefNew: 279616K-> 34944K (314560K), 0.3626923 secs] 279616K-> 146232K (1013632K), 0.3627492 secs [Times: user = 0.33 sys = 0.03, real = 0.36 sec] [GC (Allocation Fejl) [DefNew: 314560K-> 34943K (314560K), 0.4589079 secs] 425848K-> 295442K (1013632K), 0.4589526 secs [Times: user = 0.41 sys = 0.05, real = 0.46 secs) KORTstørrelse: 3000000 [Fuld GC ( System.gc ()) [Fastholdt: 260498K-> 368281K (699072K), 0.5580183 sek.] 434341K-> 368281K (1013632K), [Metaspace: 2624K-> 2624K (1056768K)], 0.5580738 sek] [Tider: bruger = 0,50 sys = 0,06, reel = 0,56 sek] [GC (Allocation Failure) [DefNew: 279616K-> 0K (314560K), 0.0076722 sec] 647897K-> 368281K (1013632K), 0.0077169 sec] [Times: user = 0.01 sys = 0.00, real = 0,01 sek] KORTstørrelse: 1000000 Programmets afslutning! Heap def ny generation i alt 314560K, brugt 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden space 279616K, 35% brugt [0x00000000c0000000, 0x00000000c61e9370, 0x00000000d1110000) fra rum 34944K, 0% brugt [0x00000000d, 0x00000000d, 0x00000000d brugt [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) fastholdt genereret i alt 699072K, brugt 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) pladsen 699072K, 52% brugt [0x00000000d5550000, 0x00000000b0000 brugt klasse plads 283K, kapacitet 386K, begået 512K, reserveret 1048576K

Vi skal være i stand til at genkende alle elementerne fra den enkle GC-log. Men der er flere nye ting.

Lad os nu overveje de nye emner i output, der er fremhævet med blåt i næste afsnit:

5.1. Fortolke en mindre GC i Young Generation

Vi begynder med at analysere de nye dele i en mindre GC:

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

Som før opdeler vi linjerne i dele:

  1. DefNew - Navn på den anvendte affaldssamler. Dette ikke så indlysende navn står for den single-threaded mark-copy stop-the-world affaldssamler og er det, der bruges til at rense den unge generation
  2. 279616K-> 34944K - Brug af den unge generation før og efter indsamling
  3. (314560K) - Den samlede størrelse af den unge generation
  4. 0,3626923 sek - Varigheden i sekunder
  5. [Gange: bruger = 0,33 sys = 0,03, reel = 0,36 sek] - GC-begivenhedens varighed målt i forskellige kategorier

Lad os nu forklare de forskellige kategorier:

  • bruger - Den samlede CPU-tid, der blev brugt af Garbage Collector
  • sys - Den tid, der bruges i OS-opkald eller på at vente på systemhændelser
  • ægte - Dette er al forløbet tid inklusive tidsskiver brugt af andre processer

Da vi kører vores eksempel ved hjælp af Serial Garbage Collector, som altid bruger kun en enkelt tråd, er realtid lig med summen af ​​bruger- og systemtider.

5.2. Fortolkning af en fuld GC

I dette næstsidste eksempel ser vi, at for en større samling (Full GC), som blev udløst af vores systemopkald, var den anvendte kollektor Fastholdt.

Det sidste stykke yderligere information, vi ser, er en opdeling efter det samme mønster for Metaspace:

[Metaspace: 2624K-> 2624K (1056768K)], 0.5580738 sek.]

Metaspace er et nyt hukommelsesrum introduceret i Java 8 og er et område med indbygget hukommelse.

5.3. Analyse af Java Heap-opdeling

Den sidste del af output inkluderer en opdeling af bunken inklusive et hukommelsesfodaftrykoversigt for hver del af hukommelsen.

Vi kan se, at Eden-rummet havde et 35% fodaftryk, og Tenured havde et 52% fodaftryk. Et resume for metadata plads og klasse plads er også inkluderet.

Fra ovenstående eksempler vi kan nu forstå nøjagtigt, hvad der skete med hukommelsesforbrug inde i JVM under GC-begivenhederne.

6. Tilføjelse af dato og klokkeslæt

Ingen god log er komplet uden dato og klokkeslæt.

Denne ekstra information kan være meget nyttig, når vi har brug for at korrelere GC-logdata med data fra andre kilder, eller det kan simpelthen hjælpe med at søge.

Vi kan tilføje følgende to argumenter, når vi kører vores applikation for at få oplysninger om dato og klokkeslæt, der skal vises i vores logfiler:

-XX: + PrintGCTimeStamps -XX: + PrintGCDateStamps

Hver linje starter nu med den absolutte dato og det tidspunkt, hvor den blev skrevet efterfulgt af et tidsstempel, der afspejler den realtid, der er gået i sekunder, siden JVM startede:

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

Bemærk, at disse tuningflag er blevet fjernet i Java 9. Det nye alternativ er:

-Xlog: gc * :: tid

7. Logge på en fil

Som vi allerede har set, er GC-loggen som standard skrevet til stdout. En mere praktisk løsning er at specificere en outputfil.

Vi kan gøre dette ved at bruge argumentet -Xloggc: hvor fil er den absolutte sti til vores outputfil:

-Xloggc: / sti / til / fil / gc.log

I lighed med andre tuningflag afskaffede Java 9 -Xloggc-flag til fordel for den nye samlede logning. For at være mere specifik er alternativet til logning til en fil nu:

-Xlog: gc: / sti / til / fil / gc.log

8. Java 9: ​​Unified JVM Logging

Fra og med Java 9 er de fleste GC-relaterede tuning-flag blevet udfaset til fordel for den samlede logningsindstilling -Xlog: gc. Det verbose: gc mulighed fungerer dog stadig i Java 9 og nyere version.

F.eks. Fra Java 9, svarende til -verbose: gc flag i det nye samlede logningssystem er:

-Xlog: gc

Dette logger alle infoniveau GC-logfiler til standardoutputtet. Det er også muligt at bruge -Xlog: gc = syntaks for at ændre logniveauet. For eksempel for at se alle fejlfindingslogfiler:

-Xlog: gc = fejlretning

Som vi så tidligere, kan vi ændre outputdestinationen via -Xlog: gc =: syntaks. Som standard er produktion er stdout, men vi kan ændre det til stderr eller endda en fil:

-Xlog: gc = debug: fil = gc.txt

Det er også muligt at tilføje et par flere felter til output ved hjælp af dekoratører. For eksempel:

-Xlog: gc = debug :: pid, tid, oppetid

Her udskriver vi proces-id, oppetid og nuværende tidsstempel i hver logerklæring.

For at se flere eksempler på Unified JVM Logging, se JEP 158-standarden.

9. A Værktøj til at analysere GC-logfiler

Det kan være tidskrævende og ret kedeligt at analysere GC-logfiler ved hjælp af en teksteditor. Afhængigt af JVM-versionen og den anvendte GC-algoritme kan GC-logformatet variere.

Der er et meget godt gratis grafisk analyseværktøj, der analyserer Garbage collection logs, giver mange metrics om potentielle Garbage Collection-problemer og endda giver potentielle løsninger på disse problemer.

Helt sikkert tjek Universal GC Log Analyzer!

10. Konklusion

For at opsummere har vi i denne vejledning undersøgt detaljeret detaljeret skraldindsamling i Java.

Først startede vi med at introducere, hvad den detaljerede affaldssamling er, og hvorfor vi måske vil bruge den. Vi kiggede derefter på flere eksempler ved hjælp af en simpel Java-applikation. Vi begyndte med at aktivere GC-logning i sin enkleste form, inden vi udforskede flere mere detaljerede eksempler og hvordan man fortolker output.

Endelig undersøgte vi flere ekstra muligheder for at logge tid og datooplysninger og hvordan man skriver information til en logfil.

Kodeeksemplerne kan findes på GitHub.