Dela via


Grunderna i skräpinsamling

I CLR (Common Language Runtime) fungerar skräpinsamlaren (GC) som en automatisk minneshanterare. Skräpinsamlaren hanterar allokering och frigörande av minne för ett program. Därför behöver utvecklare som arbetar med hanterad kod inte skriva kod för att utföra minneshanteringsuppgifter. Automatisk minneshantering kan eliminera vanliga problem som att glömma att frigöra ett objekt och orsaka en minnesläcka eller försöka komma åt ledigt minne för ett objekt som redan har frigjorts.

Den här artikeln beskriver huvudbegreppen för skräpinsamling.

Förmåner

Skräpinsamlaren har följande fördelar:

  • Frigör utvecklare från att behöva frigöra minne manuellt.

  • Allokerar objekt på den hanterade heapen effektivt.

  • Återtar objekt som inte längre används, rensar deras minne och håller minnet tillgängligt för framtida allokeringar. Hanterade objekt får automatiskt rent innehåll att börja med, så deras konstruktorer behöver inte initiera alla datafält.

  • Ger minnessäkerhet genom att se till att ett objekt inte kan använda för sig självt det minne som allokerats för ett annat objekt.

Grunderna i minnet

I följande lista sammanfattas viktiga CLR-minnesbegrepp:

  • Varje process har ett eget, separat virtuellt adressutrymme. Alla processer på samma dator delar samma fysiska minne och sidfilen, om det finns en.

  • På 32-bitarsdatorer har varje process som standard ett virtuellt adressutrymme i användarläge på 2 GB.

  • Som programutvecklare arbetar du bara med virtuellt adressutrymme och manipulerar aldrig fysiskt minne direkt. Skräpinsamlaren allokerar och frigör virtuellt minne åt dig på den hanterade heapen.

    Om du skriver intern kod använder du Windows-funktioner för att arbeta med det virtuella adressutrymmet. Dessa funktioner allokerar och frigör virtuellt minne åt dig på interna högar.

  • Virtuellt minne kan vara i tre tillstånd:

    Stat/län beskrivning
    Kostnadsfri Minnesblocket har inga referenser till det och är tillgängligt för allokering.
    Reserverad Minnesblocket är tillgängligt för din användning och kan inte användas för någon annan allokeringsbegäran. Du kan dock inte lagra data i det här minnesblocket förrän de har checkats in.
    Engagerad Minnesblocket tilldelas till fysisk lagring.
  • Det virtuella adressutrymmet kan fragmenteras, vilket innebär att det finns lediga block som kallas hål i adressutrymmet. När en virtuell minnesallokering begärs måste den virtuella minneshanteraren hitta ett enda kostnadsfritt block som är tillräckligt stort för att uppfylla allokeringsbegäran. Även om du har 2 GB ledigt utrymme misslyckas en allokering som kräver 2 GB om inte allt ledigt utrymme finns i ett enda adressblock.

  • Du kan få slut på minne om det inte finns tillräckligt med virtuellt adressutrymme för att reservera eller fysiskt utrymme att checka in.

    Sidfilen används även om det fysiska minnestrycket (efterfrågan på fysiskt minne) är lågt. Första gången det fysiska minnestrycket är högt måste operativsystemet göra plats i fysiskt minne för att lagra data och säkerhetskopiera en del av de data som finns i fysiskt minne till sidfilen. Datan är inte växlingsbar förrän den behövs, så det är möjligt att stöta på växling i situationer där det fysiska minnestrycket är lågt.

Minnesallokering

När du initierar en ny process reserverar körningen en sammanhängande region med adressutrymme för processen. Det här reserverade adressutrymmet kallas för den hanterade heapen. Den hanterade heapen behåller en pekare till adressen där nästa objekt i heapen ska allokeras. Inledningsvis är den här pekaren inställd på den hanterade heapens basadress. Alla referenstyper allokeras på den hanterade heapen. När ett program skapar den första referenstypen allokeras minne för typen på basadressen för den hanterade heapen. När programmet skapar nästa objekt allokerar körningen minne för det i adressutrymmet omedelbart efter det första objektet. Så länge adressutrymmet är tillgängligt fortsätter körningen att allokera utrymme för nya objekt på det här sättet.

Det går snabbare att allokera minne från den hanterade heapen än ohanterad minnesallokering. Eftersom körningen allokerar minne för ett objekt genom att lägga till ett värde i en pekare är det nästan lika snabbt som att allokera minne från stacken. Eftersom nya objekt som allokeras i följd lagras sammanhängande i den hanterade heapen kan ett program komma åt objekten snabbt.

Minnesfrisättning

Optimeringsmotorn för skräpinsamlaren avgör den bästa tiden för att utföra en samling baserat på de allokeringar som görs. När skräpinsamlaren utför en samling frigörs minnet för objekt som inte längre används av programmet. Den avgör vilka objekt som inte längre används genom att undersöka programmets rötter. Ett programs rötter omfattar statiska fält, lokala variabler i en tråds stack, CPU-register, GC-referenser och slutför kön. Varje rot refererar antingen till ett objekt på den hanterade heapen eller är inställt på null. Skräpinsamlaren kan be resten av körningen om dessa rötter. Skräpinsamlaren använder den här listan för att skapa ett diagram som innehåller alla objekt som kan nås från rötterna.

Objekt som inte finns i diagrammet kan inte nås från programmets rötter. Skräpinsamlaren tar hänsyn till skräp som inte kan nås och frigör det minne som allokerats för dem. Under en samling undersöker skräpinsamlaren den hanterade heapen och letar efter de block av adressutrymme som upptas av oåtkomliga objekt. När det upptäcker varje oåtkomligt objekt använder det en minneskopieringsfunktion för att komprimera de åtkomliga objekten i minnet, vilket frigör blocken med adressutrymmen som allokerats till oåtkomliga objekt. När minnet för de nåbara objekten har komprimerats gör skräpinsamlaren nödvändiga pekarkorrigeringar så att programmets rötter pekar mot objekten på deras nya platser. Den placerar också den hanterade heapens pekare efter det sista nåbara objektet.

Minnet komprimeras endast om en samling identifierar ett stort antal objekt som inte kan nås. Om alla objekt i den hanterade heapen överlever en samling behöver du inte komprimera minnet.

För att förbättra prestandan allokerar körningen minne för stora objekt i en separat heap. Skräpinsamlaren frigör automatiskt minnet för stora objekt. Men för att undvika att flytta stora objekt i minnet komprimeras det här minnet vanligtvis inte.

Villkor för en skräpinsamling

Skräpinsamling inträffar när något av följande villkor är sant:

  • Systemet har lite fysiskt minne. Minnesstorleken identifieras antingen av meddelandet om lågt minne från operativsystemet eller lågt minne, vilket anges av värden.

  • Det minne som används av allokerade objekt på den hanterade heapen överskrider ett acceptabelt tröskelvärde. Det här tröskelvärdet justeras kontinuerligt när processen körs.

  • Metoden GC.Collect anropas. I nästan alla fall behöver du inte anropa den här metoden eftersom skräpinsamlaren körs kontinuerligt. Den här metoden används främst för unika situationer och testning.

Den hanterade heapen

När CLR initierar skräpinsamlaren allokerar den ett segment av minne för att lagra och hantera objekt. Det här minnet kallas den hanterade heapen, till skillnad från en intern heap i operativsystemet.

Det finns en hanterad heap för varje hanterad process. Alla trådar i processen allokerar minne för objekt på samma heap.

För att reservera minne anropar skräpinsamlaren funktionen Windows VirtualAlloc och reserverar ett minnessegment i taget för hanterade program. Skräpinsamlaren reserverar även segment efter behov och släpper segment tillbaka till operativsystemet (efter att ha rensat dem från alla objekt) genom att anropa windows VirtualFree-funktionen .

Viktigt!

Storleken på segment som allokeras av skräpinsamlaren är implementeringsspecifik och kan ändras när som helst, inklusive i periodiska uppdateringar. Din app bör aldrig göra antaganden om eller vara beroende av en viss segmentstorlek och bör inte heller försöka konfigurera mängden minne som är tillgängligt för segmentallokeringar.

Ju färre objekt som allokeras på heapen, desto mindre arbete måste skräpinsamlaren utföra. När du allokerar objekt ska du inte använda avrundade värden som överskrider dina behov, till exempel allokera en matris med 32 byte när du bara behöver 15 byte.

När en skräpinsamling utlöses återtar skräpinsamlaren det minne som upptas av döda objekt. Återtagandeprocessen komprimerar levande objekt så att de flyttas tillsammans och det döda utrymmet tas bort, vilket gör högen mindre. Den här processen säkerställer att objekt som allokeras tillsammans håller ihop på den hanterade heapen för att bevara deras ort.

Skräpsamlingarnas intrusiveness (frekvens och varaktighet) är resultatet av allokeringsvolymen och mängden efterlämnat minne på den hanterade heapen.

Heapen kan betraktas som ackumulering av två högar: den stora objekthögen och den lilla objekthögen. Den stora objekthögen innehåller objekt som är 85 000 byte och större, vilket vanligtvis är matriser. Det är ovanligt att ett instansobjekt är extra stort.

Dricks

Du kan konfigurera tröskelvärdets storlek för objekt att gå på den stora objekthögen.

Generationer

GC-algoritmen baseras på flera överväganden:

  • Det går snabbare att komprimera minnet för en del av den hanterade heapen än för hela den hanterade heapen.
  • Nyare objekt har kortare livslängd och äldre objekt har längre livslängd.
  • Nyare objekt tenderar att vara relaterade till varandra och nås av programmet ungefär samtidigt.

Skräpinsamling sker främst med återvinning av kortlivade objekt. För att optimera skräpinsamlarens prestanda är den hanterade heapen uppdelad i tre generationer, 0, 1 och 2, så att den kan hantera långlivade och kortlivade objekt separat. Skräpinsamlaren lagrar nya objekt i generation 0. Objekt som skapas tidigt i programmets livslängd som överlever samlingar befordras och lagras i generation 1 och 2. Eftersom det går snabbare att komprimera en del av den hanterade heapen än hela heapen gör det här schemat att skräpinsamlaren kan frigöra minnet i en viss generation i stället för att frigöra minnet för hela den hanterade heapen varje gång den utför en samling.

  • Generation 0: Den här generationen är den yngsta och innehåller kortlivade objekt. Ett exempel på ett kortlivade objekt är en tillfällig variabel. Skräpinsamling sker oftast i den här generationen.

    Nyligen allokerade objekt utgör en ny generation av objekt och är implicit generation 0-samlingar. Men om de är stora objekt går de på den stora objekthögen (LOH), som ibland kallas för generation 3. Generation 3 är en fysisk generation som samlas in logiskt som en del av generation 2.

    De flesta objekt frigörs för skräpinsamling i generation 0 och överlever inte till nästa generation.

    Om ett program försöker skapa ett nytt objekt när generation 0 är full, utför skräpinsamlaren en samling för att frigöra adressutrymme för objektet. Skräpinsamlaren börjar med att undersöka objekten i generation 0 i stället för alla objekt i den hanterade heapen. En samling enbart generation 0 frigör ofta tillräckligt med minne för att programmet ska kunna fortsätta att skapa nya objekt.

  • Generation 1: Den här generationen innehåller kortlivade objekt och fungerar som en buffert mellan kortlivade objekt och långlivade objekt.

    När skräpinsamlaren utför en samling av generation 0 komprimerar den minnet för de nåbara objekten och befordrar dem till generation 1. Eftersom objekt som överlever samlingar tenderar att ha längre livslängd är det klokt att höja upp dem till en högre generation. Skräpinsamlaren behöver inte återanvända objekten i generation 1 och 2 varje gång den utför en samling av generation 0.

    Om en samling av generation 0 inte hämtar tillräckligt med minne för att programmet ska kunna skapa ett nytt objekt kan skräpinsamlaren utföra en samling av generation 1 och sedan generation 2. Objekt i generation 1 som överlever samlingar befordras till generation 2.

  • Generation 2: Den här generationen innehåller långlivade objekt. Ett exempel på ett långlivade objekt är ett objekt i ett serverprogram som innehåller statiska data som är aktiva under hela processen.

    Objekt i generation 2 som överlever en samling finns kvar i generation 2 tills de är fast beslutna att inte kunna nås i en framtida samling.

    Objekt på den stora objekthögen (som ibland kallas generation 3) samlas också in i generation 2.

Skräpsamlingar sker i vissa generationer som villkor garanterar. Att samla in en generation innebär att samla in objekt i den generationen och alla dess yngre generationer. En skräpinsamling i generation 2 kallas även för en fullständig skräpinsamling eftersom den återtar objekt i alla generationer (dvs. alla objekt i den hanterade heapen).

Överlevnad och kampanjer

Objekt som inte frigörs i en skräpinsamling kallas efterlevande och befordras till nästa generation:

  • Objekt som överlever en skräpinsamling av generation 0 befordras till generation 1.
  • Objekt som överlever en skräpinsamling av generation 1 befordras till generation 2.
  • Objekt som överlever en skräpinsamling av generation 2 finns kvar i generation 2.

När skräpinsamlaren upptäcker att överlevnadsgraden är hög i en generation ökar tröskelvärdet för allokeringar för den generationen. Nästa samling får en betydande storlek på återvunnet minne. CLR balanserar kontinuerligt två prioriteringar: att inte låta ett programs arbetsuppsättning bli för stor genom att fördröja skräpinsamlingen och inte låta skräpinsamlingen köras för ofta.

Tillfälliga generationer och segment

Eftersom objekt i generation 0 och 1 är kortlivade kallas dessa generationer för tillfälliga generationer.

Tillfälliga generationer allokeras i minnessegmentet som kallas det tillfälliga segmentet. Varje nytt segment som hämtas av skräpinsamlaren blir det nya tillfälliga segmentet och innehåller objekten som överlevde en skräpinsamling av generation 0. Det gamla tillfälliga segmentet blir det nya segmentet generation 2.

Storleken på det tillfälliga segmentet varierar beroende på om ett system är 32-bitars eller 64-bitars och vilken typ av skräpinsamlare det körs (arbetsstation eller server-GC). I följande tabell visas standardstorlekarna för det tillfälliga segmentet:

Arbetsstation/server-GC 32-bitars 64-bitars
Arbetsstation GC 16 MB 256 MB
Server GC 64 MB 4 GB
Server GC med > 4 logiska processorer 32 MB 2 GB
Server GC med > 8 logiska processorer 16 MB 1 GB

Det tillfälliga segmentet kan innehålla objekt i generation 2. Generation 2-objekt kan använda flera segment så många som din process kräver och minne tillåter.

Mängden ledigt minne från en tillfällig skräpinsamling är begränsad till storleken på det tillfälliga segmentet. Mängden minne som frigörs är proportionell mot det utrymme som upptogs av de döda objekten.

Vad händer under en skräpinsamling?

En skräpinsamling har följande faser:

  • En markeringsfas som hittar och skapar en lista över alla levande objekt.

  • En omlokaliseringsfas som uppdaterar referenserna till de objekt som ska komprimeras.

  • En kompakteringsfas som återtar utrymmet som upptas av de döda objekten och komprimerar de överlevande objekten. Komprimeringsfasen flyttar objekt som har överlevt en skräpinsamling mot den äldre delen av segmentet.

    Eftersom generation 2-samlingar kan uppta flera segment kan objekt som befordras till generation 2 flyttas till ett äldre segment. Både generation 1- och generation 2-överlevande kan flyttas till ett annat segment eftersom de befordras till generation 2.

    Vanligtvis komprimeras inte den stora objekthögen (LOH) eftersom kopiering av stora objekt medför prestandastraff. Men i .NET Core och i .NET Framework 4.5.1 och senare kan du använda GCSettings.LargeObjectHeapCompactionMode egenskapen för att komprimera den stora objekthögen på begäran. Dessutom komprimeras LOH automatiskt när en hård gräns anges genom att antingen:

Skräpinsamlaren använder följande information för att avgöra om objekten är aktiva:

  • Stackrötter: Stackvariabler som tillhandahålls av JIT-kompilatorn (just-in-time) och stack walker. JIT-optimeringar kan förlänga eller förkorta kodregioner inom vilka stackvariabler rapporteras till skräpinsamlaren.

  • Skräpinsamlingshandtag: Hanterar som pekar på hanterade objekt och som kan allokeras av användarkod eller den vanliga språkkörningen.

  • Statiska data: Statiska objekt i programdomäner som kan referera till andra objekt. Varje programdomän håller reda på sina statiska objekt.

Innan en skräpinsamling startar pausas alla hanterade trådar förutom den tråd som utlöste skräpinsamlingen.

Följande bild visar en tråd som utlöser en skräpinsamling och gör att de andra trådarna pausas:

Skärmbild av hur en tråd utlöser en skräpinsamling.

Ohanterade resurser

För de flesta objekt som programmet skapar kan du förlita dig på skräpinsamling för att utföra nödvändiga minneshanteringsuppgifter automatiskt. Ohanterade resurser kräver dock explicit rensning. Den vanligaste typen av ohanterad resurs är ett objekt som omsluter en operativsystemresurs, till exempel ett filhandtag, ett fönsterhandtag eller en nätverksanslutning. Även om skräpinsamlaren kan spåra livslängden för ett hanterat objekt som kapslar in en ohanterad resurs, har den inte specifik kunskap om hur du rensar resursen.

När du definierar ett objekt som kapslar in en ohanterad resurs rekommenderar vi att du anger nödvändig kod för att rensa den ohanterade resursen i en offentlig Dispose metod. Genom att ange en Dispose metod gör du det möjligt för användare av objektet att uttryckligen släppa resursen när de är klara med objektet. När du använder ett objekt som kapslar in en ohanterad resurs måste du anropa Dispose efter behov.

Du måste också ange ett sätt för dina ohanterade resurser att släppas om en konsument av din typ glömmer att anropa Dispose. Du kan antingen använda ett säkert handtag för att omsluta den ohanterade resursen Object.Finalize() eller åsidosätta metoden.

Mer information om hur du rensar ohanterade resurser finns i Rensa ohanterade resurser.

Se även