Automatisch geheugenbeheer

Automatisch geheugenbeheer is een van de services die de Common Language Runtime biedt tijdens beheerde uitvoering. De garbagecollector van Common Language Runtime beheert de toewijzing en het vrijlaten van geheugen voor een toepassing. Voor ontwikkelaars betekent dit dat u geen code hoeft te schrijven om geheugenbeheertaken uit te voeren wanneer u beheerde toepassingen ontwikkelt. Automatisch geheugenbeheer kan veelvoorkomende problemen voorkomen, zoals vergeten een object vrij te maken en een geheugenlek te veroorzaken of toegang te krijgen tot geheugen voor een object dat al is vrijgemaakt. In deze sectie wordt beschreven hoe de garbagecollector geheugen toewijst en vrijgeeft.

Geheugen toewijzen

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 garbagecollector geheugen toe in de adresruimte direct na het eerste object. Zolang er adresruimte beschikbaar is, blijft de garbagecollector 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 zeer snel toegang tot de objecten.

Geheugen vrijgeven

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. Elke toepassing heeft een set wortels. Elke hoofdmap verwijst naar een object in de beheerde heap of is ingesteld op null. De hoofdmappen van een toepassing bevatten statische velden, lokale variabelen en parameters op de stack van een thread en CPU-registers. De garbagecollector heeft toegang tot de lijst met actieve roots die de Just-In-Time-compiler (JIT) en de runtime onderhouden. Met behulp van deze lijst wordt de wortels van een toepassing onderzocht en in het proces wordt een grafiek gemaakt die alle objecten bevat die bereikbaar zijn vanuit de wortels.

Objecten die zich niet in de grafiek bevinden, zijn niet bereikbaar vanuit de wortels 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. Houd er rekening mee dat het geheugen alleen wordt 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 echter te voorkomen dat grote objecten in het geheugen worden verplaatst, wordt dit geheugen niet gecomprimeerd.

Generaties en prestaties

Om de prestaties van de garbagecollector te optimaliseren, is de beheerde heap onderverdeeld in drie generaties: 0, 1 en 2. Het algoritme van de garbagecollection van de runtime is gebaseerd op verschillende generalisaties die de computersoftware-industrie heeft gedetecteerd door te experimenteren met garbagecollectionschema's. Ten eerste is het sneller om het geheugen te comprimeren voor een deel van de beheerde heap dan voor de hele beheerde heap. Ten tweede hebben nieuwere objecten kortere levensduur en hebben oudere objecten langere levensduur. Ten slotte zijn nieuwere objecten meestal gerelateerd aan elkaar en worden ze rond dezelfde tijd door de toepassing geopend.

De garbagecollection van de runtime 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. Het proces van objectpromotie wordt verderop in dit onderwerp beschreven. 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.

In werkelijkheid voert de garbagecollector een verzameling uit wanneer generatie 0 vol is. Als een toepassing probeert een nieuw object te maken wanneer generatie 0 vol is, detecteert de garbagecollector dat er geen adresruimte overblijft in generatie 0 om toe te wijzen voor het object. De garbagecollector voert een verzameling uit in een poging om de adresruimte in generatie 0 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. Dit is de meest efficiƫnte benadering, omdat nieuwe objecten meestal korte levensduur hebben en het wordt verwacht dat veel van de objecten in generatie 0 niet meer worden gebruikt door de toepassing wanneer een verzameling wordt uitgevoerd. Bovendien maakt een verzameling van generatie 0 vaak voldoende geheugen vrij, zodat de toepassing nieuwe objecten kan blijven maken.

Nadat de garbagecollector een verzameling van generatie 0 heeft uitgevoerd, wordt het geheugen voor de bereikbare objecten gecomprimeerd, zoals eerder in dit onderwerp wordt uitgelegd in Geheugen vrijgeven . De garbagecollection bevordert deze objecten en beschouwt dit gedeelte van de beheerde heap generatie 1. Omdat objecten die verzamelingen overleven, langere levensduur hebben, is het zinvol om ze naar een hogere generatie te promoveren. Als gevolg hiervan hoeft de garbagecollector de objecten in generaties 1 en 2 niet telkens opnieuw te herbeleven wanneer deze een verzameling van generatie 0 uitvoert.

Nadat de garbagecollector zijn eerste verzameling van generatie 0 uitvoert en de bereikbaar objecten naar generatie 1 heeft gepromoot, wordt de rest van de beheerde heap generatie 0 beschouwd. Het blijft geheugen toewijzen voor nieuwe objecten van generatie 0 totdat generatie 0 vol is en het is nodig om een andere verzameling uit te voeren. Op dit moment bepaalt de optimalisatie-engine van de garbagecollector of het noodzakelijk is om de objecten in oudere generaties te onderzoeken. Als een verzameling van generatie 0 bijvoorbeeld onvoldoende geheugen vrijgeeft voor de toepassing om de poging tot het maken van een nieuw object te voltooien, kan de garbagecollector een verzameling van generatie 1 en vervolgens generatie 2 uitvoeren. Als dit onvoldoende geheugen vrijgeeft, kan de garbagecollection een verzameling van generaties 2, 1 en 0 uitvoeren. Na elke verzameling compacteert de garbagecollector de bereikbaar objecten van generatie 0 en bevordert deze tot generatie 1. Objecten in generatie 1 die verzamelingen overleven, worden gepromoveerd tot generatie 2. Omdat de garbagecollector slechts drie generaties ondersteunt, blijven objecten in generatie 2 die een verzameling overleven in generatie 2 totdat ze in een toekomstige verzameling onbereikbaar zijn.

Geheugen vrijgeven voor niet-beheerde resources

Voor het merendeel van de objecten die door uw toepassing worden gemaakt, kunt u vertrouwen op de garbagecollector 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 maakt 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 verwijderingsmethode . Door een verwijderingsmethode op te geven, stelt u gebruikers van uw object in staat om het geheugen expliciet vrij te maken wanneer ze klaar zijn met het object. Wanneer u een object gebruikt dat een niet-beheerde resource inkapselt, moet u rekening houden met Verwijderen en indien nodig aanroepen. Zie Garbagecollection voor meer informatie over het opschonen van onbeheerde resources en een voorbeeld van een ontwerppatroon voor het implementeren van Verwijdering.

Zie ook