De Garbage Collector (GC) in .NET

De Garbage Collector (GC) zorgt voor het toewijzen en vrijgeven van geheugen voor de applicatie. Elke keer als er een object wordt aangemaakt dan wijst de common language runtime (CLR) geheugen van de managed heap toe aan dit object. Een heap is een verzameling van aaneengesloten stukken geheugen. Dit geheugen wordt tijdens het starten van het proces (applicatie) gereserveerd door de runtime en is bij aanvang helemaal leeg. De grootte van de heap is afhanklijk van een aantal factoren: de .NET Framework versie, is het een 32 of 64 bits systeem en in welke mode wordt de GC uitgevoerd, server of werkstation. De managed heap houdt een pointer bij waar het volgende object opgeslagen moet worden. Een heap kan meerdere segments hebben. Als een segement vol is, wordt er binnen dezelfde heap een nieuw segment aangemaakt.

Garbage collector roots
Een object wordt pas opgeruimd door de GC als deze niet meer direct te benaderen is vanaf de GC root. De GC kent vier verschillende roots:
  1. Lokale variabelen binnen een methode
  2. Variabelen die zijn gedeclareerd als static
  3. Een managed object die wordt doorgegeven aan een unmanaged object, een COM+ library. COM+ kent geen GC, maar maakt gebruikt van reference count. Als er geen references meer zijn, wordt het geheugen vrijgegeven.
  4. Als een object een finalizer heeft wordt het een speciale root. Dit object wacht totdat het .NET Framework zelf de finalizer methode aanroept.
De GC root zelf is geen object, maar refereert naar objecten. Alle objecten die door de GC roots worden gerefereerd zullen niet opgeruimd worden.

Small object heap en de large object heap
Wanneer er geheugen moet worden toegewezen aan een object kijkt de runtime eerst hoe groot het object is. Als een object groter is dan 85000 bytes, dan wordt deze op de large object heap segment (LOH) geplaatst. Alle objecten kleiner dan 85000 worden op de small object heap segment geplaatst (SOH). De SOH en LOH zitten beide in de managed heap. Voor beiden geldt dat als het segment vol is, er een nieuwe wordt aangemaakt in dezelfde heap. De segmenten worden pas verwijderd als deze enkel dode instanties bevatten.

Generaties
De GC in .NET kent drie generaties: 0, 1 en 2. Nieuwe objecten worden vrijwel altijd in de eerste generatie opgeslagen. Wanneer de GC langskomt en het object overleeft deze ronde, dan wordt dit object verplaatst naar de tweede generatie. De tweede generatie wordt gezien als een buffer tussen de eerste en derde generatie. Als het object de opruiming van de tweede generatie overleeft, dan wordt deze verplaatst naar de derde generatie.

Het idee van de generaties is dat nieuwe objecten hoogstwaarschijnlijk een korte levensduur hebben. Denk hierbij aan lokale variabelen en parameters. Als na deze verzameling voldoende geheugen vrij is gemaakt, dan worden de tweede en derde generaties met rust gelaten. Indien de GC bij hogere generaties komt, dan komt hij ook altijd langs de lagere generaties. Bij dit gehele proces wordt de LOH met rust gelaten want zij bestaan buiten de eerste, tweede en derde generatie.

Instanties van objecten worden in elk segment sequentieel opgeslagen. Dit maakt het toewijzen van geheugen aan objecten extreem snel.

Wanneer de Garbage Collector inspecteert
De GC treedt niet op op basis van een tijdsinterval. Er zijn drie triggers waar de GC op reageert:
  1. Als het segment van generatie 0 vol is.
  2. Als via code GC.Collect() wordt aangeroepen
  3. Als de runtime van het OS het signaal ontvangt dat het geheugen dreigt vol te lopen (OutOfMemory)
Het opruimen van objecten in generatie 2 gebeurt alleen als een nieuw object niet meer in het segment past. Dit betekent dat in het geval van 64-bits processors opruimingen in generatie 2 zelden voorkomen. Dit heeft als gevolg dat veel geheugen gereserveerd blijft zonder dat het daadwerkelijk wordt gebruikt.

Het wordt afgeraden om handmatig GC.Collect() aan te roepen omdat dit het proces verstoort die het .NET Framework zelf aanstuurt. Wat er namelijk gebeurt is dat objecten die zijn aangemaakt sinds de laatste inspectie, te vroeg worden beschouwd als generatie 2 objecten. Dit betekent dat er objecten in generatie twee terecht kunnen komen die daar eigenlijk niet thuishoren.

De GC onderneemt elke keer de volgende stappen:
  1. 'Pauzeer' alle threads die .NET aanroepen maken.
  2. Bepaal welke objecten er kunnen worden verzameld die niet meer kunnen worden bereikt door de GC root.
  3. Verwijder alle objecten die zijn gemarkeerd als verwijderd.
  4. Verplaats de overgebleven objecten naar het einde van het segment (dit duurt het langst)
  5. Laat alle threads weer doorgaan
Bovenstaande stappen worden enkel uitgevoerd op de SOH. De LOH wordt met rust gelaten. Dit is gedaan omdat het te veel kost om de objecten in de LOH opnieuw te adresseren. Als een object wordt verwijderd uit de LOH wordt verder niks gedaan. Dit betekent dat er tussen de objecten gaten ontstaan met vrije ruimte. Op het moment dat later een nieuw object moet worden geplaatst op de LOH, dan wordt er gekeken of er tussen bestaande objecten vrije ruimte is en als dat zo is en het object past daar, dan wordt het object daar opgeslagen.

Soorten Garbage Collectors
Er zijn twee verschillende manieren waarop de GC kan worden uitgevoerd:
  1. Concurrent: Deze manier wordt toegepast op werkstations
  2. Synchroon: Deze manier wordt toegepast op servers. Een voorbeeld hiervan is ASP.NET

Reacties

Populaire posts van deze blog

[SQL Server] varchar vs nvarchar

MS Sql 70-461: Chapter 5

[C#] Class serialiseren en deserialiseren