Grondbeginselen van garbagecollection
In de Common Language Runtime (CLR) fungeert de garbagecollector (GC) als een automatische geheugenbeheerder. De garbagecollector beheert de toewijzing en het vrijgeven van geheugen voor een toepassing. Daarom hoeven ontwikkelaars die met beheerde code werken geen code te schrijven om geheugenbeheertaken uit te voeren. Automatisch geheugenbeheer kan veelvoorkomende problemen voorkomen, zoals vergeten een object vrij te maken en een geheugenlek te veroorzaken of toegang te krijgen tot vrijgekomen geheugen voor een object dat al is vrijgemaakt.
In dit artikel worden de kernconcepten van garbagecollection beschreven.
Vergoedingen
De garbagecollector biedt de volgende voordelen:
Ontwikkelaars hoeven geen geheugen handmatig vrij te geven.
Hiermee worden objecten op de beheerde heap efficiënt toegewezen.
Maakt objecten vrij die niet meer worden gebruikt, wist hun geheugen en houdt het geheugen beschikbaar voor toekomstige toewijzingen. Beheerde objecten krijgen automatisch schone inhoud om mee te beginnen, zodat hun constructors niet elk gegevensveld hoeven te initialiseren.
Biedt geheugenveiligheid door ervoor te zorgen dat een object niet kan worden gebruikt voor zichzelf het geheugen dat voor een ander object is toegewezen.
Basisprincipes van geheugen
De volgende lijst bevat een overzicht van belangrijke CLR-geheugenconcepten:
Elk proces heeft een eigen, afzonderlijke virtuele adresruimte. Alle processen op dezelfde computer delen hetzelfde fysieke geheugen en het paginabestand, indien aanwezig.
Op 32-bits computers heeft elk proces standaard een virtuele adresruimte van 2 GB in de gebruikersmodus.
Als ontwikkelaar van toepassingen werkt u alleen met virtuele adresruimte en bewerkt u nooit rechtstreeks fysiek geheugen. De garbagecollector wijst virtuele geheugen toe en vrijt het virtuele geheugen voor u op de beheerde heap.
Als u systeemeigen code schrijft, gebruikt u Windows-functies om met de virtuele adresruimte te werken. Deze functies wijzen en gratis virtueel geheugen voor u toe op systeemeigen heaps.
Virtueel geheugen kan in drie statussen zijn:
Toestand Beschrijving Gratis Het geheugenblok bevat geen verwijzingen naar het geheugen en is beschikbaar voor toewijzing. Gereserveerd Het geheugenblok is beschikbaar voor uw gebruik en kan niet worden gebruikt voor een andere toewijzingsaanvraag. U kunt echter geen gegevens opslaan in dit geheugenblok totdat deze zijn doorgevoerd. Toegewijd Het geheugenblok wordt toegewezen aan fysieke opslag. Virtuele adresruimte kan worden gefragmenteerd, wat betekent dat er vrije blokken zijn die bekend staan als gaten in de adresruimte. Wanneer een toewijzing van virtueel geheugen wordt aangevraagd, moet de virtuele geheugenbeheerder één vrij blok vinden dat groot genoeg is om te voldoen aan de toewijzingsaanvraag. Zelfs als u 2 GB vrije ruimte hebt, is een toewijzing waarvoor 2 GB vereist, mislukt, tenzij al die vrije ruimte zich in één adresblok bevindt.
U kunt onvoldoende geheugen hebben als er onvoldoende virtuele adresruimte is om te reserveren of fysieke ruimte om door te voeren.
Het paginabestand wordt gebruikt, zelfs als de fysieke geheugenbelasting (vraag naar fysiek geheugen) laag is. De eerste keer dat de fysieke geheugenbelasting hoog is, moet het besturingssysteem ruimte in het fysieke geheugen maken om gegevens op te slaan en wordt een back-up van een aantal gegevens in het fysieke geheugen naar het paginabestand uitgevoerd. De gegevens worden pas gepagineerd als ze nodig zijn, dus het is mogelijk om paging te tegenkomen in situaties waarin de fysieke geheugendruk laag is.
Geheugentoewijzing
Wanneer u een nieuw proces initialiseert, behoudt de runtime een aaneengesloten regio met adresruimte voor het proces. Deze gereserveerde adresruimte wordt de beheerde heap genoemd. De beheerde heap onderhoudt een aanwijzer naar het adres waar het volgende object in de heap wordt toegewezen. In eerste instantie wordt deze aanwijzer ingesteld op het basisadres van de beheerde heap. Alle referentietypen worden toegewezen aan de beheerde heap. Wanneer een toepassing het eerste referentietype maakt, wordt geheugen toegewezen voor het type op het basisadres van de beheerde heap. Wanneer de toepassing het volgende object maakt, wijst de runtime er geheugen aan toe in de adresruimte direct na het eerste object. Zolang er adresruimte beschikbaar is, blijft de runtime op deze manier ruimte toewijzen voor nieuwe objecten.
Het toewijzen van geheugen van de beheerde heap is sneller dan de toewijzing van niet-beheerde geheugen. Omdat de runtime geheugen toewijst aan een object door een waarde toe te voegen aan een aanwijzer, is het bijna net zo snel als het toewijzen van geheugen van de stack. Bovendien, omdat nieuwe objecten die opeenvolgend worden toegewezen, aaneengesloten worden opgeslagen in de beheerde heap, heeft een toepassing snel toegang tot de objecten.
Geheugenrelease
De optimalisatie-engine van de garbagecollector bepaalt de beste tijd om een verzameling uit te voeren op basis van de toewijzingen die worden gemaakt. Wanneer de garbagecollector een verzameling uitvoert, wordt het geheugen vrijgegeven voor objecten die niet meer door de toepassing worden gebruikt. Hiermee wordt bepaald welke objecten niet meer worden gebruikt door de wortels van de toepassing te onderzoeken. De hoofdmappen van een toepassing omvatten statische velden, lokale variabelen op de stack van een thread, CPU-registers, GC-ingangen en de uiteindelijke wachtrij. Elke hoofdmap verwijst naar een object in de beheerde heap of is ingesteld op null. De garbagecollector kan de rest van de runtime voor deze wortels vragen. De garbagecollector gebruikt deze lijst om een grafiek te maken die alle objecten bevat die bereikbaar zijn vanuit de wortels.
Objecten die zich niet in de grafiek bevinden, zijn niet bereikbaar vanuit de hoofdmappen van de toepassing. De garbagecollector beschouwt onbereikbare objecten garbage en geeft het toegewezen geheugen vrij. Tijdens een verzameling onderzoekt de garbagecollector de beheerde heap, op zoek naar de blokken adresruimte die wordt bezet door onbereikbare objecten. Terwijl elk onbereikbaar object wordt gedetecteerd, gebruikt het een functie voor het kopiëren van geheugen om de bereikbare objecten in het geheugen te comprimeren, waardoor de blokken adresruimten worden vrijgemaakt die zijn toegewezen aan onbereikbare objecten. Zodra het geheugen voor de bereikbare objecten is gecomprimeerd, plaatst de garbagecollector de benodigde aanwijzercorrecties zodat de wortels van de toepassing verwijzen naar de objecten op hun nieuwe locaties. Ook wordt de aanwijzer van de beheerde heap na het laatst bereikbaar object geplaatst.
Geheugen wordt alleen gecomprimeerd als een verzameling een aanzienlijk aantal onbereikbare objecten detecteert. Als alle objecten in de beheerde heap een verzameling overleven, is er geen geheugencompressie nodig.
Ter verbetering van de prestaties wijst de runtime geheugen toe voor grote objecten in een afzonderlijke heap. De garbagecollector geeft automatisch het geheugen vrij voor grote objecten. Om te voorkomen dat grote objecten in het geheugen worden verplaatst, wordt dit geheugen meestal niet gecomprimeerd.
Voorwaarden voor een garbagecollection
Garbagecollection treedt op wanneer aan een van de volgende voorwaarden wordt voldaan:
Het systeem heeft weinig fysiek geheugen. De geheugengrootte wordt gedetecteerd door de melding over weinig geheugen van het besturingssysteem of onvoldoende geheugen, zoals aangegeven door de host.
Het geheugen dat wordt gebruikt door toegewezen objecten op de beheerde heap overschrijdt een acceptabele drempelwaarde. Deze drempelwaarde wordt continu aangepast wanneer het proces wordt uitgevoerd.
De GC.Collect methode wordt aangeroepen. In bijna alle gevallen hoeft u deze methode niet aan te roepen omdat de garbagecollector continu wordt uitgevoerd. Deze methode wordt voornamelijk gebruikt voor unieke situaties en tests.
De beheerde heap
Nadat de CLR de garbagecollector heeft geïnitialiseerd, wijst het een segment van het geheugen toe om objecten op te slaan en te beheren. Dit geheugen wordt de beheerde heap genoemd, in plaats van een systeemeigen heap in het besturingssysteem.
Er is een beheerde heap voor elk beheerd proces. Alle threads in het proces wijzen geheugen toe voor objecten op dezelfde heap.
Als u geheugen wilt reserveren, roept de garbagecollector de Functie Windows VirtualAlloc aan en reserveert deze één segment geheugen tegelijk voor beheerde toepassingen. De garbagecollector reserveert indien nodig ook segmenten en brengt segmenten terug naar het besturingssysteem (nadat ze van objecten zijn gewist) door de functie Windows VirtualFree aan te roepen.
Belangrijk
De grootte van segmenten die door de garbagecollector worden toegewezen, is implementatiespecifiek en kan op elk gewenst moment worden gewijzigd, ook in periodieke updates. Uw app mag nooit veronderstellingen maken over of afhankelijk zijn van een bepaalde segmentgrootte, en moet ook niet proberen om de hoeveelheid geheugen te configureren die beschikbaar is voor segmenttoewijzingen.
Hoe minder objecten aan de heap zijn toegewezen, hoe minder werk de garbagecollector moet doen. Wanneer u objecten toewijst, gebruikt u geen afgeronde waarden die uw behoeften overschrijden, zoals het toewijzen van een matrix van 32 bytes wanneer u slechts 15 bytes nodig hebt.
Wanneer een garbagecollection wordt geactiveerd, maakt de garbagecollector het geheugen vrij dat wordt bezet door dode objecten. Het vrijmakende proces compacteert levende objecten, zodat ze samen worden verplaatst en de dode ruimte wordt verwijderd, waardoor de heap kleiner wordt. Dit proces zorgt ervoor dat objecten die samen worden toegewezen, bij elkaar blijven op de beheerde heap om hun lokaliteit te behouden.
De intrusieve (frequentie en duur) van garbagecollecties is het resultaat van het volume van toewijzingen en de hoeveelheid overlevend geheugen op de beheerde heap.
De heap kan worden beschouwd als de accumulatie van twee heaps: de grote object heap en de kleine object heap. De grote object-heap bevat objecten die 85.000 bytes en groter zijn. Dit zijn meestal matrices. Het is zeldzaam dat een exemplaarobject extra groot is.
Tip
U kunt de drempelwaarde voor objecten zo configureren dat ze op de grote object-heap gaan.
Generaties
Het GC-algoritme is gebaseerd op verschillende overwegingen:
- Het is sneller om het geheugen te comprimeren voor een deel van de beheerde heap dan voor de hele beheerde heap.
- Nieuwere objecten hebben kortere levensduur en oudere objecten hebben langere levensduur.
- Nieuwere objecten zijn meestal gerelateerd aan elkaar en worden rond dezelfde tijd door de toepassing geopend.
Garbagecollection vindt voornamelijk plaats bij het terughalen van kortdurende objecten. Om de prestaties van de garbagecollector te optimaliseren, is de beheerde heap onderverdeeld in drie generaties, 0, 1 en 2, zodat deze objecten met een lange levensduur afzonderlijk kunnen verwerken. De garbagecollection slaat nieuwe objecten op in generatie 0. Objecten die vroeg in de levensduur van de toepassing zijn gemaakt en die overlevende verzamelingen worden gepromoveerd en opgeslagen in generaties 1 en 2. Omdat het sneller is om een deel van de beheerde heap te comprimeren dan de hele heap, kan de garbagecollector het geheugen vrijgeven in een specifieke generatie in plaats van het geheugen vrij te geven voor de hele beheerde heap telkens wanneer een verzameling wordt uitgevoerd.
Generatie 0: Deze generatie is de jongste en bevat kortlevende objecten. Een voorbeeld van een object met een korte levensduur is een tijdelijke variabele. Garbagecollection vindt het vaakst plaats in deze generatie.
Nieuw toegewezen objecten vormen een nieuwe generatie objecten en zijn impliciet verzamelingen van de 0e generatie. Als ze echter grote objecten zijn, gaan ze op de grote object heap (LOH), die soms generatie 3 wordt genoemd. Generatie 3 is een fysieke generatie die logisch wordt verzameld als onderdeel van generatie 2.
De meeste objecten worden teruggevorderd voor garbagecollection in generatie 0 en overleven niet tot de volgende generatie.
Als een toepassing probeert een nieuw object te maken wanneer generatie 0 vol is, voert de garbagecollector een verzameling uit om de adresruimte voor het object vrij te maken. De garbagecollector begint met het onderzoeken van de objecten in generatie 0 in plaats van alle objecten in de beheerde heap. Een verzameling van generatie 0 alleen maakt vaak voldoende geheugen vrij om de toepassing in staat te stellen nieuwe objecten te maken.
Generatie 1: Deze generatie bevat kortlevende objecten en fungeert als buffer tussen objecten met korte levensduur en langlevende objecten.
Nadat de garbagecollector een verzameling van generatie 0 heeft uitgevoerd, wordt het geheugen voor de bereikbare objecten gecomprimeerd en naar generatie 1 gepromoot. Omdat objecten die verzamelingen overleven, langere levensduur hebben, is het zinvol om ze naar een hogere generatie te promoveren. De garbagecollector hoeft de objecten in generaties 1 en 2 niet telkens opnieuw te herbeleven wanneer deze een verzameling van generatie 0 uitvoert.
Als een verzameling van generatie 0 onvoldoende geheugen vrijgeeft voor de toepassing om een nieuw object te maken, kan de garbagecollector een verzameling van generatie 1 en vervolgens generatie 2 uitvoeren. Objecten in generatie 1 die verzamelingen overleven, worden gepromoveerd tot generatie 2.
Generatie 2: Deze generatie bevat langlevende objecten. Een voorbeeld van een langlevend object is een object in een servertoepassing die statische gegevens bevat die live zijn voor de duur van het proces.
Objecten in generatie 2 die een verzameling overleven, blijven in generatie 2 totdat ze in een toekomstige verzameling onbereikbaar zijn.
Objecten op de grote object heap (ook wel generatie 3 genoemd) worden ook verzameld in generatie 2.
Garbagecollection vindt plaats in specifieke generaties als voorwaarden rechtvaardigen. Het verzamelen van een generatie betekent het verzamelen van objecten in die generatie en alle jongere generaties. Een garbagecollection van de tweede generatie wordt ook wel een volledige garbagecollection genoemd, omdat het objecten in alle generaties terugvordert (dat wil gezegd alle objecten in de beheerde heap).
Overleving en promoties
Objecten die niet worden vrijgemaakt in een garbagecollection worden overlevenden genoemd en worden gepromoveerd naar de volgende generatie:
- Objecten die een garbagecollection van de 0e generatie overleven, worden gepromoveerd tot generatie 1.
- Objecten die een garbagecollection van de eerste generatie overleven, worden gepromoveerd tot generatie 2.
- Objecten die een garbagecollection van de tweede generatie overleven, blijven in generatie 2.
Wanneer de garbagecollector detecteert dat de overlevingssnelheid in een generatie hoog is, wordt de drempelwaarde voor toewijzingen voor die generatie verhoogd. De volgende verzameling krijgt een aanzienlijke grootte van vrijgemaakt geheugen. De CLR verwijst voortdurend naar twee prioriteiten: de werkset van een toepassing wordt niet te groot door garbagecollection uit te stellen en de garbagecollection niet te vaak uit te voeren.
Kortstondige generaties en segmenten
Omdat objecten in generaties 0 en 1 kortlevend zijn, worden deze generaties de kortstondige generaties genoemd.
Tijdelijke generaties worden toegewezen in het geheugensegment dat het tijdelijke segment wordt genoemd. Elk nieuw segment dat door de garbagecollector wordt verkregen, wordt het nieuwe kortstondige segment en bevat de objecten die een afvalverzameling van de 0 generatie hebben overleefd. Het oude kortstondige segment wordt het nieuwe generatie 2-segment.
De grootte van het tijdelijke segment varieert, afhankelijk van of een systeem 32-bits of 64-bits is en op het type garbagecollector dat wordt uitgevoerd (werkstation of server GC). In de volgende tabel ziet u de standaardgrootten van het tijdelijke segment:
Werkstation/server GC | 32-bits | 64-bits |
---|---|---|
Werkstation GC | 16 MB | 256 MB |
Server GC | 64 MB | 4 GB |
Server GC met > 4 logische CPU's | 32 MB | 2 GB |
Server GC met > 8 logische CPU's | 16 MB | 1 GB |
Het kortstondige segment kan objecten van de tweede generatie bevatten. Objecten van de tweede generatie kunnen meerdere segmenten gebruiken als uw proces vereist en geheugen mogelijk maakt.
De hoeveelheid vrijgemaakt geheugen van een kortstondige garbagecollection is beperkt tot de grootte van het tijdelijke segment. De hoeveelheid geheugen die vrij is, is evenredig met de ruimte die door de dode objecten is bezet.
Wat gebeurt er tijdens een garbagecollection?
Een garbagecollection heeft de volgende fasen:
Een markeringsfase waarmee een lijst met alle live-objecten wordt gevonden en gemaakt.
Een herlocatiefase waarmee de verwijzingen naar de objecten worden bijgewerkt die worden gecomprimeerd.
Een compacte fase die de ruimte vrij maakt die door de dode objecten wordt bezet en de overlevende objecten compacteert. De compacte fase verplaatst objecten die een garbagecollection hebben overleefd naar het oudere einde van het segment.
Omdat verzamelingen van de tweede generatie meerdere segmenten kunnen bezetten, kunnen objecten die worden gepromoveerd tot generatie 2, worden verplaatst naar een ouder segment. Zowel generatie 1 als generatie 2 overlevenden kunnen worden verplaatst naar een ander segment omdat ze worden gepromoveerd tot generatie 2.
Normaal gesproken wordt de grote object heap (LOH) niet gecomprimeerd omdat het kopiëren van grote objecten een prestatiestraf oplegt. In .NET Core en in .NET Framework 4.5.1 en hoger kunt u de GCSettings.LargeObjectHeapCompactionMode eigenschap echter gebruiken om de grote object-heap op aanvraag te comprimeren. Bovendien wordt de LOH automatisch gecomprimeerd wanneer een vaste limiet wordt ingesteld door een van de volgende opties op te geven:
- Een geheugenlimiet voor een container.
- De configuratieopties GCHeapHardLimit of GCHeapHardLimitPercent runtime.
De garbagecollector gebruikt de volgende informatie om te bepalen of objecten live zijn:
Stack-roots: Stack-variabelen die worden geleverd door de Just-In-Time-compiler (JIT) en stack walker. JIT-optimalisaties kunnen regio's met code inkorten of verkorten waarin stackvariabelen worden gerapporteerd aan de garbagecollector.
Garbagecollection-ingangen: handles die verwijzen naar beheerde objecten en die kunnen worden toegewezen door gebruikerscode of de algemene taalruntime.
Statische gegevens: statische objecten in toepassingsdomeinen die kunnen verwijzen naar andere objecten. Elk toepassingsdomein houdt de statische objecten bij.
Voordat een garbagecollection wordt gestart, worden alle beheerde threads onderbroken, met uitzondering van de thread die de garbagecollection heeft geactiveerd.
In de volgende afbeelding ziet u een thread die een garbagecollection activeert en ervoor zorgt dat de andere threads worden onderbroken:
Onbeheerde resources
Voor de meeste objecten die uw toepassing maakt, kunt u vertrouwen op garbagecollection om automatisch de benodigde geheugenbeheertaken uit te voeren. Niet-beheerde resources vereisen echter expliciet opschonen. Het meest voorkomende type onbeheerde resource is een object dat een besturingssysteemresource verpakt, zoals een bestandsgreep, venstergreep of netwerkverbinding. Hoewel de garbagecollector de levensduur van een beheerd object kan bijhouden dat een niet-beheerde resource inkapselt, heeft deze geen specifieke kennis over het opschonen van de resource.
Wanneer u een object definieert dat een niet-beheerde resource inkapselt, wordt u aangeraden de benodigde code op te geven voor het opschonen van de onbeheerde resource in een openbare Dispose
methode. Door een Dispose
methode op te geven, stelt u gebruikers van uw object in staat om de resource expliciet vrij te geven wanneer ze klaar zijn met het object. Wanneer u een object gebruikt dat een niet-beheerde resource inkapselt, moet u deze indien nodig aanroepen Dispose
.
U moet ook een manier opgeven om uw onbeheerde resources vrij te geven voor het geval een consument van uw type vergeet te bellen Dispose
. U kunt een veilige ingang gebruiken om de niet-beheerde resource te verpakken of de Object.Finalize() methode te overschrijven.
Zie Onbeheerde resources opschonen voor meer informatie over het opschonen van onbeheerde resources.