Garbage Collection in Java

Het verzamelen van afval in Java is een van de geavanceerde onderwerpen. Kennis van Java GC helpt ons bij het fijnafstemmen van de prestaties van onze applicatieruntime.

Garbage Collection in Java

  • In Java hoeven programmeurs zich niet bezig te houden met het vernietigen van objecten die niet meer in gebruik zijn. De Garbage Collector zorgt daarvoor.
  • De Garbage Collector is een Daemon-thread die op de achtergrond blijft draaien. Het maakt in feite de heap-geheugen vrij door ontoegankelijke objecten te vernietigen.
  • Ontoegankelijke objecten zijn degene die niet langer worden gerefereerd door een deel van het programma.
  • We kunnen de garbage collector voor ons Java-programma kiezen via JVM-opties, we zullen hier later in deze tutorial naar kijken.

Hoe Automatische Garbage Collection werkt?

Automatische garbage collection is een proces waarbij naar de heap-geheugen wordt gekeken, onbereikbare objecten worden geïdentificeerd (ook wel “markering” genoemd) en deze worden vernietigd door compactie. Een probleem met deze aanpak is dat, naarmate het aantal objecten toeneemt, de tijd voor garbage collection blijft toenemen, omdat het door de hele lijst met objecten moet gaan, op zoek naar het onbereikbare object. Echter, de empirische analyse van toepassingen toont aan dat de meeste objecten kortstondig zijn. Dit gedrag werd gebruikt om de prestaties van de JVM te verbeteren, en de aangenomen methodologie wordt vaak Generational Garbage Collection genoemd. Bij deze methode is de heapruimte verdeeld in generaties zoals de Young Generation, Old of Tenured Generation, en Permanent Generation. De heapruimte voor de Young generation is nieuw waar alle nieuwe objecten worden gemaakt. Zodra het vol is, vindt er een kleine garbage collection plaats (ook bekend als Minor GC). Dit betekent dat alle dode objecten uit deze generatie worden vernietigd. Dit proces is snel omdat, zoals we kunnen zien uit het diagram, de meeste ervan dood zouden zijn. De overlevende objecten in de young generation worden ouder en verplaatsen uiteindelijk naar de oudere generaties. De Old Generation wordt gebruikt om lang overlevende objecten op te slaan. Typisch wordt er een drempel ingesteld voor het jonge generatie object en wanneer die leeftijd is bereikt, wordt het object verplaatst naar de oude generatie. Uiteindelijk moet de oude generatie worden verzameld. Dit evenement wordt een Major GC (major garbage collection) genoemd. Vaak is dit veel langzamer omdat het alle levende objecten omvat. Ook is er een Full GC, wat betekent dat de hele heap wordt opgeschoond – zowel de Young als de oudere generatieruimtes. Ten slotte was er tot Java 7 een Permanent Generation (of Perm Gen), die metadata bevatte die door de JVM nodig was om de klassen en methoden te beschrijven die in de toepassing werden gebruikt. Dit werd verwijderd in Java 8.

Java Garbage Collectors

De JVM biedt eigenlijk vier verschillende garbage collectors, allemaal generatie-gebaseerd. Elk ervan heeft zijn eigen voor- en nadelen. De keuze van welke garbage collector te gebruiken ligt bij ons en er kunnen dramatische verschillen zijn in doorvoer en applicatie-onderbrekingen. Allemaal verdelen ze de beheerde heap in verschillende segmenten, gebruikmakend van de eeuwenoude veronderstellingen dat de meeste objecten in de heap van korte duur zijn en snel gerecycled moeten worden. Dus, de vier soorten garbage collectors zijn:

Serial GC

Dit is de eenvoudigste garbage collector, ontworpen voor single-threaded systemen en kleine heapgroottes. Het bevriest alle toepassingen tijdens het werk. Kan worden ingeschakeld met de -XX:+UseSerialGC JVM-optie.

Parallel/Throughput GC

Dit is de standaardcollector van de JVM in JDK 8. Zoals de naam al aangeeft, gebruikt het meerdere threads om door de heapruimte te scannen en compactie uit te voeren. Een nadeel van deze collector is dat het de toepassing pauzeert terwijl het bezig is met het uitvoeren van een kleine of volledige GC. Het is het meest geschikt voor toepassingen die dergelijke pauzes kunnen verwerken en probeert de CPU-overhead veroorzaakt door de collector te optimaliseren.

De CMS-collector

Het CMS-collector (“concurrent-mark-sweep”)-algoritme gebruikt meerdere threads (“concurrent”) om door de heap (“mark”) te scannen op ongebruikte objecten die kunnen worden gerecycled (“sweep”). Deze collector gaat over in Stop-The-World(STW)-modus in twee gevallen: – Tijdens het initialiseren van de initiële markering van wortels, d.w.z. objecten in de oude generatie die bereikbaar zijn vanuit ingangspunten van threads of statische variabelen. – Wanneer de toepassing de toestand van de heap heeft gewijzigd terwijl het algoritme gelijktijdig werd uitgevoerd en het dwingt om terug te gaan en enkele laatste aanpassingen te doen om ervoor te zorgen dat de juiste objecten zijn gemarkeerd. Deze collector kan te maken krijgen met promotiefouten. Als sommige objecten van de jonge generatie naar de oude generatie moeten worden verplaatst en de collector niet genoeg tijd heeft gehad om ruimte te maken in de oude generatieruimte, treedt er een promotiefout op. Om dit te voorkomen, kunnen we meer van de heapgrootte toewijzen aan de oude generatie of meer achtergrondthreads aan de collector leveren.

G1 verzamelaar

Als laatste maar niet de minste is de Garbage-First verzamelaar, ontworpen voor heapgroottes groter dan 4GB. Het verdeelt de heapgrootte in regio’s variërend van 1 MB tot 32 MB, gebaseerd op de heapgrootte. Er is een gelijktijdige wereldwijde markeringsfase om de levendigheid van objecten door de hele heap te bepalen. Nadat de markeringsfase is voltooid, weet G1 welke regio’s voornamelijk leeg zijn. Het verzamelt eerst onbereikbare objecten uit deze regio’s, wat meestal een grote hoeveelheid vrije ruimte oplevert. Dus G1 verzamelt deze regio’s (met afval) eerst, en vandaar de naam Garbage-First. G1 maakt ook gebruik van een pauzepredictiemodel om een door de gebruiker gedefinieerd pauzetijddoel te halen. Het selecteert het aantal regio’s dat moet worden verzameld op basis van het gespecificeerde pauzetijddoel. De G1 garbage collection-cyclus omvat de fasen zoals getoond in de figuur:

  1. Alleen-jonge-fase: deze fase omvat alleen objecten van de jonge generatie en promoveert ze naar de oude generatie. De overgang tussen de alleen-jonge-fase en de ruimte-terugwinningfase begint wanneer de oude generatie bezet is tot een bepaalde drempel, d.w.z. de Initiële heap-bezettingsdrempel. Op dit moment plant G1 een initiële markering alleen-jonge verzameling in plaats van een reguliere alleen-jonge verzameling.

  2. Initiële Markering: Dit type collectie start het markeerproces naast een reguliere jonge-only collectie. Concurrent marking bepaalt alle momenteel levende objecten in de oude generatie-regio’s die moeten worden bewaard voor de daaropvolgende ruimteherstelfase. Terwijl het markeren nog niet volledig is voltooid, kunnen reguliere jonge-only collecties plaatsvinden. Het markeren eindigt met twee speciale stop-de-wereld-pauzes: Remark en Cleanup.

  3. Remark: Deze pauze finaliseert het markeren zelf en voert globale referentieverwerking en het ontladen van klassen uit. Tussen Remark en Cleanup berekent G1 een samenvatting van de levendheidsinformatie gelijktijdig, die zal worden afgerond en gebruikt in de Cleanup-pauze om interne gegevensstructuren bij te werken.

  4. Opschonen: Deze pauze neemt ook de volledig lege regio’s en bepaalt of er daadwerkelijk een fase van ruimteherwinning zal volgen. Als er een fase van ruimteherwinning volgt, wordt de fase van alleen jonge generatie voltooid met een enkele verzameling van alleen jonge generatie.

  5. Fase van ruimteherwinning: Deze fase bestaat uit meerdere gemengde verzamelingen – naast regio’s van de jonge generatie, worden ook levende objecten van regio’s van de oude generatie geëvacueerd. De fase van ruimteherwinning eindigt wanneer G1 bepaalt dat het evacueren van meer regio’s van de oude generatie niet genoeg vrije ruimte zou opleveren die de moeite waard is.

G1 kan worden ingeschakeld met de -XX:+UseG1GC vlag. Deze strategie vermindert de kans dat de heap leegraakt voordat de achtergrondthreads klaar zijn met het scannen naar ontoegankelijke objecten. Bovendien compacteert het de heap on-the-go, iets wat de CMS-collector alleen kan doen in STW-modus. In Java 8 wordt een mooie optimalisatie geboden met de G1-collector, genaamd string deduplicatie. Zoals we weten, nemen de karakterreeksen die onze strings vertegenwoordigen veel ruimte in beslag op onze heap. Er is een nieuwe optimalisatie toegepast waarmee de G1-collector in staat is om strings te identificeren die meer dan eens worden gedupliceerd over onze heap en deze te wijzigen zodat ze naar dezelfde interne char[] array wijzen, om te voorkomen dat meerdere kopieën van dezelfde string onnodig in de heap aanwezig zijn. We kunnen de JVM-optie -XX:+UseStringDeduplication gebruiken om deze optimalisatie in te schakelen. G1 is de standaardgarbagecollector in JDK 9.

Java 8 PermGen en Metaspace

Zoals eerder vermeld, is de Permanent Generation-ruimte verwijderd sinds Java 8. Dus nu gebruikt de JDK 8 HotSpot JVM het native geheugen voor de representatie van klassenmetadata, dat Metaspace wordt genoemd. De meeste toewijzingen voor de klassenmetadata worden gedaan uit het native geheugen. Ook is er een nieuwe vlag, MaxMetaspaceSize, om de hoeveelheid geheugen die wordt gebruikt voor klassenmetadata te beperken. Als we de waarde hiervoor niet specificeren, wordt Metaspace tijdens runtime aangepast aan de vraag van de draaiende applicatie. Garbage collection voor Metaspace wordt geactiveerd wanneer het gebruik van klassenmetadata de limiet van MaxMetaspaceSize bereikt. Overmatige garbage collection voor Metaspace kan een symptoom zijn van geheugenlekken in klassen, classloaders of onvoldoende dimensionering voor onze applicatie. Dat is het wat betreft de Garbage Collection in Java. Ik hoop dat je het begrip hebt gekregen over de verschillende garbage collectors die we hebben in Java. Referenties: Oracle-documentatie, G1 GC.

Source:
https://www.digitalocean.com/community/tutorials/garbage-collection-in-java