Uitgebreide garbagecollection in Java

1. Overzicht

In deze tutorial we zullen bekijken hoe u uitgebreide garbagecollection in een Java-toepassing kunt inschakelen. We beginnen met te introduceren wat uitgebreide garbage collection is en waarom het nuttig kan zijn.

Vervolgens bekijken we verschillende voorbeelden en leren we over de verschillende beschikbare configuratie-opties. Bovendien, we zullen ons ook concentreren op het interpreteren van de uitvoer van onze uitgebreide logboeken.

Bekijk ons ​​artikel over Java Garbage Collectors voor meer informatie over Garbage Collection (GC) en de verschillende beschikbare implementaties.

2. Korte inleiding tot uitgebreide garbagecollection

Het inschakelen van uitgebreide logboekregistratie van garbagecollection is vaak vereist bij het afstemmen en debuggen van veel problemen, vooral geheugenproblemen. Sommigen zouden zelfs beweren dat we, om de gezondheid van onze applicaties strikt te bewaken, altijd de Garbage Collection-prestaties van de JVM moeten bewaken.

Zoals we zullen zien, is het GC-logboek een zeer belangrijk hulpmiddel om mogelijke verbeteringen aan de heap- en GC-configuratie van onze applicatie te onthullen. Voor elke GC-gebeurtenis geeft het GC-logboek exacte gegevens over de resultaten en duur ervan.

Na verloop van tijd kan analyse van deze informatie ons helpen het gedrag van onze applicatie beter te begrijpen en de prestaties van onze applicatie af te stemmen. Bovendien, het kan helpen de GC-frequentie en verzameltijden te optimaliseren door de beste heapgroottes, andere JVM-opties en alternatieve GC-algoritmen op te geven.

2.1. Een eenvoudig Java-programma

We gebruiken een eenvoudig Java-programma om te demonstreren hoe we onze GC-logboeken kunnen inschakelen en interpreteren:

openbare klasse Toepassing {privé statische kaart stringContainer = nieuwe HashMap (); public static void main (String [] args) {System.out.println ("Start van programma!"); String stringWithPrefix = "stringWithPrefix"; // Laad Java Heap met 3 M java.lang.String-instanties voor (int i = 0; i <3000000; i ++) {String newString = stringWithPrefix + i; stringContainer.put (newString, newString); } System.out.println ("MAP-grootte:" + stringContainer.size ()); // Expliciete GC! System.gc (); // Verwijder 2 M uit 3 M voor (int i = 0; i <2000000; i ++) {String newString = stringWithPrefix + i; stringContainer.remove (newString); } System.out.println ("MAP-grootte:" + stringContainer.size ()); System.out.println ("Einde van programma!"); }}

Zoals we in het bovenstaande voorbeeld kunnen zien, laadt dit eenvoudige programma 3 miljoen Draad instanties in een Kaart voorwerp. We maken dan een expliciete oproep naar de garbage collector met Systeem.gc ().

Ten slotte verwijderen we 2 miljoen van de Draad instanties van de Kaart. We gebruiken ook expliciet System.out.println om het interpreteren van de output gemakkelijker te maken.

In de volgende sectie zullen we zien hoe u GC-logboekregistratie activeert.

3. Activeren van "eenvoudige" GC Logging

Laten we beginnen met het uitvoeren van ons programma en uitgebreide GC inschakelen via onze JVM-opstartargumenten:

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

Het belangrijkste argument hier is de -verbose: gc, die het loggen van garbage collection-informatie in de eenvoudigste vorm activeert. Standaard wordt het GC-logboek geschreven naar stdout en zou een lijn moeten produceren voor elke jonge generatie GC en elke volledige GC.

Voor de doeleinden van ons voorbeeld hebben we de seriële garbage collector, de eenvoudigste GC-implementatie, gespecificeerd via het argument -XX: + UseSerialGC.

We hebben ook een minimale en maximale heapgrootte van 1024 MB ingesteld, maar er zijn natuurlijk meer JVM-parameters die we kunnen afstemmen.

3.1. Basiskennis van de uitgebreide uitvoer

Laten we nu eens kijken naar de uitvoer van ons eenvoudige programma:

Start programma! [GC (Allocation Failure) 279616K-> 146232K (1013632K), 0,3318607 sec] [GC (Allocation Failure) 425848K-> 295442K (1013632K), 0,4266943 sec] MAP-grootte: 3000000 [Full GC (System.gc ()) 434341K- > 368279K (1013632K), 0,5420611 sec] [GC (Allocation Failure) 647895K-> 368280K (1013632K), 0,0075449 sec] MAP-grootte: 1000000 Einde van programma!

In de bovenstaande uitvoer kunnen we al veel nuttige informatie zien over wat er in de JVM gebeurt.

In eerste instantie kan deze uitvoer er behoorlijk ontmoedigend uitzien, maar laten we het nu stap voor stap doornemen.

Allereerst, we kunnen zien dat er vier inzamelingen hebben plaatsgevonden, een volledige GC en drie opruimende jonge generaties.

3.2. De uitgebreide uitvoer in meer detail

Laten we de uitvoerlijnen in meer detail opsplitsen om precies te begrijpen wat er aan de hand is:

  1. GC of Volledige GCHet type Garbage Collection, ook niet GC of Volledige GC om een ​​kleine of volledige garbage collection te onderscheiden
  2. (Toewijzingsfout) of (System.gc ()) - De oorzaak van de verzameling - Allocation Failure geeft aan dat er geen ruimte meer was in Eden om onze objecten toe te wijzen
  3. 279616K-> 146232K - Het bezette heap-geheugen voor respectievelijk na de GC (gescheiden door een pijl)
  4. (1013632K) - De huidige capaciteit van de hoop
  5. 0,3318607 sec - De duur van het GC-evenement in seconden

Dus als we de eerste regel nemen, 279616K-> 146232K (1013632K) betekent dat de GC het bezette heapgeheugen heeft verminderd van 279616K naar 146232K. De heapcapaciteit op het moment van GC was 1013632K, en de WG nam 0.3318607 seconden.

Hoewel het eenvoudige GC-logboekformaat nuttig kan zijn, biedt het echter beperkte details. We kunnen bijvoorbeeld niet zeggen of de GC objecten van de jonge naar de oude generatie heeft verplaatst of wat de totale omvang van de jonge generatie was voor en na elke verzameling.

Om die reden is gedetailleerde GC-logging nuttiger dan de eenvoudige.

4. Activeren van "gedetailleerde" GC-logboekregistratie

Om de gedetailleerde GC-logging te activeren, gebruiken we het argument -XX: + PrintGCDetails. Dit geeft ons meer details over elke GC, zoals:

  • Grootte van de jonge en oude generatie voor en na elke GC
  • De tijd die nodig is voor een GC om te gebeuren bij jonge en oude generatie
  • De grootte van objecten die bij elke GC worden gepromoot
  • Een samenvatting van de grootte van de totale hoop

In het volgende voorbeeld zullen we zien hoe we nog meer gedetailleerde informatie kunnen vastleggen in onze logboeken door ze te combineren -verbose: gc met dit extra argument.

Houd er rekening mee dat de -XX: + PrintGCDetails flag is verouderd in Java 9, ten gunste van het nieuwe uniforme logging-mechanisme (hierover later meer). In ieder geval, het nieuwe equivalent van de -XX: + PrintGCDetails is de -Xlog: gc * keuze.

5. De "gedetailleerde" uitgebreide uitvoer interpreteren

Laten we ons voorbeeldprogramma opnieuw uitvoeren:

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

Deze keer is de uitvoer wat meer uitgebreid:

Start programma! [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 sec] [GC (Allocation Failure) [DefNew: 314560K-> 34943K (314560K), 0,4589079 sec.] 425848K-> 295442K (1013632K), 0,4589526 sec] [Times: user = 0,41 sys = 0,05, real = 0,46 sec] MAP-grootte: 3000000 [Full GC ( System.gc ()) [Tenured: 260498K-> 368281K (699072K), 0,5580183 seconden] 434341K-> 368281K (1013632K), [Metaspace: 2624K-> 2624K (1056768K)], 0,5580738 seconden] [Tijden: gebruiker = 0,50 sys = 0,06, reëel = 0,56 sec] [GC (toewijzingsfout) [DefNieuw: 279616K-> 0K (314560K), 0,0076722 sec] 647897K-> 368281K (1013632K), 0,0077169 sec] [Tijden: gebruiker = 0,01 sys = 0,00, reëel = 0,01 sec] MAP-grootte: 1000000 Einde van programma! Heap def nieuwe generatie totaal 314560K, gebruikt 100261K [0x00000000c0000000, 0x00000000d5550000, 0x00000000d5550000) eden ruimte 279616K, 35% gebruikt [0x00000000c0000000, 0x00000000c61e9370, 0x00000000% ruimte 279616K, 35% gebruikt [0x00000000c0000000, 0x00000000c61e9370, 0x00000000% 04400x500, 04400d00x500, 04400d00d0000, 04400d0000d500, 04400d0000d gebruikt [0x00000000d1110000, 0x00000000d1110000, 0x00000000d3330000) tenured generatie totale 699072K, gebruikt 368281K [0x00000000d5550000, 0x0000000100000000, 0x0000000100000000) speelt 699072K, 52% gebruikt [0x00000000d5550000, 0x00000000ebcf65e0, 0x00000000ebcf6600, 0x0000000100000000) metaSpace gebruikt 2637K, capaciteit 4486K, toegewijd 4864K, gereserveerde 1056768K klasseruimte gebruikt 283K, capaciteit 386K, vastgelegd 512K, gereserveerd 1048576K

We zouden alle elementen uit het eenvoudige GC-logboek moeten kunnen herkennen. Maar er zijn verschillende nieuwe items.

Laten we nu eens kijken naar de nieuwe items in de uitvoer die in het volgende gedeelte blauw worden gemarkeerd:

5.1. Een minor GC in Young Generation interpreteren

We beginnen met het analyseren van de nieuwe onderdelen in een kleine GC:

  • [GC (Allocation Failure) [DefNew: 279616K-> 34944K (314560K), 0,3626923 sec] 279616K-> 146232K (1013632K), 0,3627492 sec] [Times: user = 0,33 sys = 0,03, reëel = 0,36 sec]

Zoals eerder zullen we de regels opsplitsen in delen:

  1. DefNieuw - Naam van de gebruikte vuilnisman. Deze niet zo voor de hand liggende naam staat voor de single-threaded mark-copy stop-the-world garbage collector en is wat wordt gebruikt om de Young-generatie schoon te maken
  2. 279616K-> 34944K - Gebruik van de jonge generatie voor en na inzameling
  3. (314560K) - De totale omvang van de jonge generatie
  4. 0.3626923 sec - De duur in seconden
  5. [Tijden: gebruiker = 0,33 sys = 0,03, reëel = 0,36 sec] - Duur van het GC-evenement, gemeten in verschillende categorieën

Laten we nu de verschillende categorieën uitleggen:

  • gebruiker - De totale CPU-tijd die is verbruikt door Garbage Collector
  • sys - De tijd doorgebracht in OS-oproepen of wachten op systeemgebeurtenissen
  • echt - Dit is alle verstreken tijd, inclusief tijdschijven die door andere processen worden gebruikt

Omdat we ons voorbeeld gebruiken met de Serial Garbage Collector, die altijd slechts één thread gebruikt, is real-time gelijk aan de som van gebruikers- en systeemtijden.

5.2. Een volledige GC interpreteren

In dit voorlaatste voorbeeld zien we dat voor een grote verzameling (volledige GC), die werd geactiveerd door onze systeemoproep, de gebruikte verzamelaar was Ambtshalve.

Het laatste stukje aanvullende informatie dat we zien is een uitsplitsing volgens hetzelfde patroon voor de Metaspace:

[Metaspace: 2624K-> 2624K (1056768K)], 0,5580738 seconden]

Metaspace is een nieuwe geheugenruimte geïntroduceerd in Java 8 en is een gebied met native geheugen.

5.3. Java Heap Breakdown Analysis

Het laatste deel van de uitvoer bevat een uitsplitsing van de heap, inclusief een samenvatting van de geheugenvoetafdruk voor elk deel van het geheugen.

We kunnen zien dat Eden-ruimte een voetafdruk van 35% had en Tenured een voetafdruk van 52%. Een samenvatting voor metagegevensruimte en klasruimte is ook opgenomen.

Uit de bovenstaande voorbeelden, we kunnen nu precies begrijpen wat er gebeurde met het geheugengebruik in de JVM tijdens de GC-evenementen.

6. Datum- en tijdinformatie toevoegen

Geen goed logboek is compleet zonder datum- en tijdinformatie.

Deze extra informatie kan zeer nuttig zijn wanneer we GC-loggegevens moeten correleren met gegevens uit andere bronnen, of het kan het zoeken eenvoudig vergemakkelijken.

We kunnen de volgende twee argumenten toevoegen wanneer we onze applicatie uitvoeren om datum- en tijdinformatie in onze logboeken te laten verschijnen:

-XX: + PrintGCTimeStamps -XX: + PrintGCDateStamps

Elke regel begint nu met de absolute datum en tijd waarop deze werd geschreven, gevolgd door een tijdstempel die de realtime weergeeft die in seconden is verstreken sinds de JVM is gestart:

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

Houd er rekening mee dat deze afstemmingsvlaggen zijn verwijderd in Java 9. Het nieuwe alternatief is:

-Xlog: gc * :: tijd

7. Aanmelden bij een bestand

Zoals we al hebben gezien, wordt standaard naar het GC-logboek geschreven stdout. Een meer praktische oplossing is om een ​​uitvoerbestand te specificeren.

We kunnen dit doen door het argument te gebruiken -Xloggc: waar het dossier is het absolute pad naar ons outputbestand:

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

Net als bij andere afstemmingsvlaggen heeft Java 9 de vlag -Xloggc gedeprecieerd ten gunste van de nieuwe uniforme logboekregistratie. Om specifieker te zijn, is het alternatief voor inloggen op een bestand nu:

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

8. Java 9: ​​Unified JVM-logboekregistratie

Vanaf Java 9 zijn de meeste GC-gerelateerde afstemmingsvlaggen verouderd ten gunste van de uniforme logboekoptie -Xlog: gc. De uitgebreid: gc optie werkt echter nog steeds in Java 9 en nieuwere versies.

Vanaf Java 9 is het equivalent van de -verbose: gc vlag in het nieuwe uniforme logboeksysteem is:

-Xlog: gc

Hiermee logt u alle GC-logboeken van het infoniveau in de standaarduitvoer. Het is ook mogelijk om de -Xlog: gc = syntaxis om het logboekniveau te wijzigen. Om bijvoorbeeld alle logboeken op foutopsporingsniveau te zien:

-Xlog: gc = debug

Zoals we eerder zagen, kunnen we de uitvoerbestemming wijzigen via de -Xlog: gc =: syntaxis. Standaard is het output is stdout, maar we kunnen het wijzigen in stderr of zelfs een bestand:

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

Het is ook mogelijk om nog een paar velden aan de uitvoer toe te voegen met behulp van decorateurs. Bijvoorbeeld:

-Xlog: gc = debug :: pid, tijd, uptime

Hier drukken we de proces-id, uptime en huidige tijdstempel in elke loginstructie af.

Zie de JEP 158-standaard voor meer voorbeelden van Unified JVM Logging.

9. A Tool om GC-logboeken te analyseren

Het kan tijdrovend en behoorlijk vervelend zijn om GC-logboeken te analyseren met een teksteditor. Afhankelijk van de JVM-versie en het GC-algoritme dat wordt gebruikt, kan het GC-log-formaat verschillen.

Er is een zeer goede gratis grafische analyse-tool die de Garbage collection-logboeken analyseert, veel statistieken biedt over mogelijke Garbage Collection-problemen en zelfs mogelijke oplossingen voor deze problemen biedt.

Bekijk zeker de Universal GC Log Analyzer!

10. Conclusie

Samenvattend hebben we in deze tutorial uitgebreid ingegaan op uitgebreide garbagecollection in Java.

Ten eerste zijn we begonnen met te introduceren wat uitgebreide garbage collection is en waarom we het misschien willen gebruiken. We hebben vervolgens verschillende voorbeelden bekeken met behulp van een eenvoudige Java-applicatie. We zijn begonnen met het inschakelen van GC-logboekregistratie in de eenvoudigste vorm voordat we verschillende meer gedetailleerde voorbeelden verkenden en hoe we de uitvoer moesten interpreteren.

Ten slotte hebben we verschillende extra opties onderzocht voor het loggen van tijd- en datuminformatie en het schrijven van informatie naar een logbestand.

De codevoorbeelden zijn te vinden op GitHub.