Verbose søppel samling i Java

1. Oversikt

I denne veiledningen, vi tar en titt på hvordan du slår på detaljert søppelinnsamling i et Java-program. Vi begynner med å introdusere hva detaljert søppelinnsamling er og hvorfor det kan være nyttig.

Deretter ser vi på flere forskjellige eksempler, og vi lærer om de forskjellige tilgjengelige konfigurasjonsalternativene. I tillegg Vi vil også fokusere på hvordan vi skal tolke utdataene fra våre detaljerte logger.

For å lære mer om Garbage Collection (GC) og de forskjellige implementeringene som er tilgjengelige, sjekk ut artikkelen vår om Java Garbage Collectors.

2. Kort introduksjon til utdypende søppelinnsamling

Det er ofte nødvendig å slå på detaljert loggføring av søppel når du stiller inn og feilsøker mange problemer, spesielt minneproblemer. Faktisk vil noen hevde at for å strengt overvåke applikasjonens helse, bør vi alltid overvåke JVMs Garbage Collection-ytelse.

Som vi får se, er GC-loggen et veldig viktig verktøy for å avsløre potensielle forbedringer av heap- og GC-konfigurasjonen av applikasjonen vår. For hver GC som skjer, gir GC-loggen eksakte data om resultatene og varigheten.

Over tid kan analyse av denne informasjonen hjelpe oss å bedre forstå oppførselen til applikasjonen vår og hjelpe oss med å innstille applikasjonens ytelse. Videre det kan bidra til å optimalisere GC-frekvens og samlingstider ved å spesifisere de beste dyngstørrelsene, andre JVM-alternativer og alternative GC-algoritmer.

2.1. Et enkelt Java-program

Vi bruker et enkelt Java-program for å demonstrere hvordan du aktiverer og tolker GC-loggene våre:

public class Application {private static Map stringContainer = new HashMap (); public static void main (String [] args) {System.out.println ("Start av programmet!"); String stringWithPrefix = "stringWithPrefix"; // Last 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 ()); // Eksplisitt GC! System.gc (); // Fjern 2 M av 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 ("Programslutt!"); }}

Som vi kan se i eksemplet ovenfor, laster dette enkle programmet inn 3 millioner String tilfeller til en Kart gjenstand. Vi ringer deretter en eksplisitt samtale til søppeloppsamleren ved hjelp av System.gc ().

Til slutt fjerner vi 2 millioner av String forekomster fra Kart. Vi bruker også eksplisitt System.out.println for å gjøre det enklere å tolke utdataene.

I neste avsnitt ser vi hvordan du aktiverer GC-logging.

3. Aktivering av "enkel" GC-logging

La oss begynne med å kjøre programmet vårt og aktivere detaljert GC via JVM-oppstartsargumenter:

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

Det viktige argumentet her er -verbose: gc, som aktiverer loggingen av informasjon om søppelinnsamling i sin enkleste form. Som standard er GC-loggen skrevet til stdout og skal sende ut en linje for hver ung generasjons GC og hver full GC.

I forbindelse med eksemplet vårt har vi spesifisert den serielle søppeloppsamleren, den enkleste GC-implementeringen, via argumentet -XX: + UseSerialGC.

Vi har også satt en minimal og maksimal haugestørrelse på 1024 MB, men det er selvfølgelig flere JVM-parametere vi kan stille inn.

3.1. Grunnleggende forståelse av den detaljerte produksjonen

La oss nå se på utdataene fra vårt enkle program:

Start av programmet! [GC (Allocation Failure) 279616K-> 146232K (1013632K), 0.3318607 sec] [GC (Allocation Failure) 425848K-> 295442K (1013632K), 0.4266943 secs) MAP size: 3000000 [Full GC (System.gc ()) 434341K- > 368279K (1013632K), 0.5420611 sek] [GC (Allocation Failure) 647895K-> 368280K (1013632K), 0.0075449 sec] KARTSTørrelse: 1000000 Programmets slutt!

I ovenstående utgang kan vi allerede se mye nyttig informasjon om hva som skjer inne i JVM.

Først kan denne utgangen se ganske skremmende ut, men la oss nå gå gjennom den trinn for trinn.

Først av alt, vi kan se at fire samlinger fant sted, en full GC og tre rengjøring av unge generasjoner.

3.2. Den detaljerte produksjonen mer detaljert

La oss nedbryte utgangslinjene mer detaljert for å forstå nøyaktig hva som skjer:

  1. GC eller Full GCEnten typen søppelinnsamling GC eller Full GC for å skille ut en mindre eller full søppelsamling
  2. (Tildelingsfeil) eller (System.gc ()) - Årsaken til samlingen - Tildelingsfeil indikerer at det ikke var mer plass i Eden til å fordele objektene våre
  3. 279616K-> 146232K - Det okkuperte haugeminnet henholdsvis før og etter GC (atskilt med en pil)
  4. (1013632K) - Haugens nåværende kapasitet
  5. 0.3318607 sek - Varigheten av GC-hendelsen i sekunder

Dermed, hvis vi tar første linje, 279616K-> 146232K (1013632K) betyr at GC reduserte okkupert haugeminne fra 279616K til 146232K. Haugekapasiteten på GC var 1013632K, og GC tok 0.3318607 sekunder.

Selv om det enkle GC-loggningsformatet kan være nyttig, gir det imidlertid begrensede detaljer. For eksempel kan vi ikke fortelle om GC flyttet noen objekter fra den unge til den gamle generasjonen, eller hva som var den totale størrelsen på den unge generasjonen før og etter hver samling.

Av den grunn er detaljert GC-logging mer nyttig enn den enkle.

4. Aktivering av "detaljert" GC-logging

For å aktivere detaljert GC-logging bruker vi argumentet -XX: + PrintGCDetails. Dette vil gi oss flere detaljer om hver GC, for eksempel:

  • Størrelsen på den unge og gamle generasjonen før og etter hver GC
  • Tiden det tar for en GC å skje i unge og gamle generasjoner
  • Størrelsen på objekter som markedsføres ved hver GC
  • Et sammendrag av størrelsen på den totale dyngen

I det neste eksemplet ser vi hvordan du kan fange enda mer detaljert informasjon i loggene våre -verbose: gc med dette ekstra argumentet.

Vær oppmerksom på at -XX: + PrintGCDetails flagget er avviklet i Java 9, til fordel for den nye enhetlige loggingsmekanismen (mer om dette senere). Uansett, den nye ekvivalenten til -XX: + PrintGCDetails er den -Xlog: gc * alternativ.

5. Tolke den "detaljerte" detaljerte utdata

La oss kjøre eksempelprogrammet vårt igjen:

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

Denne gangen er produksjonen ganske mer ordentlig:

Start av programmet! [GC (Allocation Failure) [DefNew: 279616K-> 34944K (314560K), 0.3626923 sec] 279616K-> 146232K (1013632K), 0.3627492 sec] [Times: user = 0.33 sys = 0.03, real = 0.36 secs] [GC (Allocation Feil) [DefNew: 314560K-> 34943K (314560K), 0.4589079 secs] 425848K-> 295442K (1013632K), 0.4589526 secs [Times: user = 0.41 sys = 0.05, real = 0.46 secs) KARTSTørrelse: 3000000 [Full GC ( System.gc ()) [Tenured: 260498K-> 368281K (699072K), 0.5580183 secs] 434341K-> 368281K (1013632K), [Metaspace: 2624K-> 2624K (1056768K)], 0.5580738 sec] [Times: user = 0.50 sys = 0,06, real = 0,56 sek] [GC (Allocation Failure) [DefNew: 279616K-> 0K (314560K), 0.0076722 secs] 647897K-> 368281K (1013632K), 0.0077169 sec] [Times: user = 0.01 sys = 0.00, real = 0.01 sek] KARTstørrelse: 1000000 Programslutt! Heap def ny generasjon totalt 314560K, brukt 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden space 279616K, 35% brukt [0x00000000c0000000, 0x00000000c61e9370, 0x00000000d1110000) fra rom 34944K, 0% brukt [0x00000000d, 0x00000000d, 0x00000000d brukt [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) tenured generation total 699072K, brukt 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) areal 699072K, 52% brukt [0x00000000d5550000, 0x00000000b0000 klasseplass brukt 283K, kapasitet 386K, forpliktet 512K, reservert 1048576K

Vi skal kunne gjenkjenne alle elementene fra den enkle GC-loggen. Men det er flere nye ting.

La oss nå vurdere de nye elementene i utgangen som er uthevet i blått i neste avsnitt:

5.1. Tolker en mindre GC i Young Generation

Vi begynner med å analysere de nye delene i en mindre GC:

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

Som før deler vi linjene i deler:

  1. DefNew - Navnet på søppeloppsamleren som brukes. Dette ikke så åpenbare navnet står for den ensfargede merkekopi stopp-verden-søppeloppsamleren og er det som brukes til å rense den unge generasjonen
  2. 279616K-> 34944K - Bruk av den unge generasjonen før og etter innsamling
  3. (314560K) - Den totale størrelsen på den unge generasjonen
  4. 0,3626923 sek - Varigheten i sekunder
  5. [Tider: bruker = 0,33 sys = 0,03, reell = 0,36 sek] - Varigheten av GC-hendelsen, målt i forskjellige kategorier

La oss nå forklare de forskjellige kategoriene:

  • bruker - Den totale CPU-tiden som ble brukt av Garbage Collector
  • sys - Tiden brukt i OS-samtaler eller venting på systemhendelser
  • ekte - Dette er all forløpt tid inkludert tidsskiver som brukes av andre prosesser

Siden vi kjører eksemplet vårt med Serial Garbage Collector, som alltid bruker bare en tråd, er sanntid lik summen av bruker- og systemtider.

5.2. Tolker en full GC

I dette nest siste eksemplet ser vi at for en større samling (Full GC), som ble utløst av systemanropet vårt, var samleren som ble brukt Fastholdt.

Den siste informasjonen vi ser er en oversikt over samme mønster for Metaspace:

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

Metaspace er et nytt minneområde introdusert i Java 8 og er et område med eget minne.

5.3. Analyse av Java Heap Breakdown

Den siste delen av utgangen inkluderer en oversikt over dyngen, inkludert et sammendrag av minnefotavtrykk for hver del av minnet.

Vi kan se at Eden space hadde et 35% fotavtrykk og Tenured hadde et 52% footprint. Et sammendrag for Metadata space og class space er også inkludert.

Fra eksemplene ovenfor, vi kan nå forstå nøyaktig hva som skjedde med minneforbruk inne i JVM under GC-hendelsene.

6. Legge til dato og klokkeslettinformasjon

Ingen god logg er komplett uten dato og klokkeslett.

Denne ekstra informasjonen kan være veldig nyttig når vi trenger å korrelere GC-loggdata med data fra andre kilder, eller det kan ganske enkelt bidra til å søke.

Vi kan legge til følgende to argumenter når vi kjører applikasjonen vår for å få informasjon om dato og klokkeslett som skal vises i loggene våre:

-XX: + PrintGCTimeStamps -XX: + PrintGCDateStamps

Hver linje begynner nå med den absolutte datoen og klokkeslettet da den ble skrevet etterfulgt av en tidsstempel som gjenspeiler sanntid i sekunder siden JVM startet:

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

Vær oppmerksom på at disse innstillingsflaggene er fjernet i Java 9. Det nye alternativet er:

-Xlog: gc * :: tid

7. Logge på en fil

Som vi allerede har sett, er GC-loggen som standard skrevet til stdout. En mer praktisk løsning er å spesifisere en utdatafil.

Vi kan gjøre dette ved å bruke argumentet -Xloggc: hvor fil er den absolutte banen til utdatafilen vår:

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

I likhet med andre tuningflagg avskaffet Java 9 -Xloggc-flagget til fordel for den nye enhetlige loggføringen. For å være mer spesifikk, er alternativet for å logge på en fil nå:

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

8. Java 9: ​​Unified JVM Logging

Fra og med Java 9 har de fleste GC-relaterte innstillingsflagg blitt avviklet til fordel for det enhetlige loggingsalternativet -Xlog: gc. De uttalt: gc alternativet, men fungerer fortsatt i Java 9 og nyere versjon.

For eksempel, fra og med Java 9, tilsvarer -verbose: gc flagget i det nye enhetlige loggingssystemet er:

-Xlog: gc

Dette vil logge alle infonivå GC-loggene til standardutgangen. Det er også mulig å bruke -Xlog: gc = syntaksen for å endre loggnivået. For eksempel, for å se alle feilsøkingsloggene:

-Xlog: gc = feilsøking

Som vi så tidligere, kan vi endre output-destinasjonen via -Xlog: gc =: syntaks. Som standard er produksjon er stdout, men vi kan endre det til stderr eller til og med en fil:

-Xlog: gc = feilsøking: fil = gc.txt

Det er også mulig å legge til noen flere felt i utgangen ved hjelp av dekoratører. For eksempel:

-Xlog: gc = feilsøking :: pid, tid, oppetid

Her skriver vi ut prosess-ID, oppetid og gjeldende tidsstempel i hver loggerklæring.

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

9. A Verktøy for å analysere GC-logger

Det kan være tidkrevende og ganske kjedelig å analysere GC-logger ved hjelp av en tekstredigerer. Avhengig av JVM-versjonen og GC-algoritmen som brukes, kan GC-loggformatet variere.

Det er et veldig bra gratis grafisk analyseverktøy som analyserer søppeloppsamlingsloggene, gir mange beregninger om potensielle søppeloppsamlingsproblemer, og til og med gir potensielle løsninger på disse problemene.

Definitivt sjekk ut Universal GC Log Analyzer!

10. Konklusjon

For å oppsummere, i denne veiledningen, har vi utforsket detaljert detaljert søppelinnsamling i Java.

Først begynte vi med å introdusere hva det er omstendelig søppeloppsamling og hvorfor vi kanskje vil bruke den. Vi så på flere eksempler ved hjelp av et enkelt Java-program. Vi begynte med å aktivere GC-logging i sin enkleste form før vi utforsket flere mer detaljerte eksempler og hvordan man tolker utdataene.

Til slutt utforsket vi flere ekstra alternativer for logging av tid og datoinformasjon og hvordan du skriver informasjon til en loggfil.

Kodeeksemplene finner du på GitHub.


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