Hur står det till med minnet - Del 1?
Av: Andreas Håkansson
Publicerad: 2004-04-27
Med lanseringen av .NET introducerade Microsoft en beprövad metod för minneshantering, Garbage Collection. I grova drag innebär det att Du oftast inte behöver oroa dig över att reservera och frigöra minne som du behöver, då det sker per automatik. Trots det är det viktigt att du, som utvecklare, har förståelse för hur minneshanteringen fungerar då det kan påverka så väl ditt sätt att skriva program, som externa resurser.
Grunderna för minneshantering Då minneshantering är ett område som det bedrivs mycket forskning på för att optimera användningen av minnet har det genom tiderna funnits en rad olika lösningar som ansetts vara den mest effektiva. Under åren som har gått har man gjort fler och fler framsteg inom optimeringen men de underliggande principerna för minneshantering har varit de samma:
De tillsynes enkla stegen har varit källan till många problem inom programutveckling. Ett vanligt problem är att man försöker använda minne man inte ännu reserverat eller som man redan har frigjort. Vad som skiljer olika utvecklingsmiljöer åt är hur de har valt att hantera de olika stegen, samt till vilken grad du som utvecklare är involverad. Minneshantering i .NET Framework Microsoft valde att utveckla en stack-baserad miljö när de skapade .NET Framework. Det innebär att den kod som skall exekveras läggs på en stack1 och hämtas ut vid själva exekveringen. Förenklat kan man säga att varje rad kod som .NET exekvera hamnar som en egen post på stacken, vilket innebär det minne som en variabel använder sig av frigörs automatiskt när posten som den använder plockas ut från stacken. Varje exekverande tråd tilldelas en egen stack vilket gör att man inte har direkt åtkomst av variabler mellan olika trådar. Denna typ av minneshantering är en sanning med en viss modifikation, då Microsoft även har delat upp variabler i två kategorier:
Det är viktigt att du som utvecklare förstår skillnaden mellan de två typerna då de har stor inverkan på hur minneshanteringen i .NET fungerar. En värdetyp skapas direkt på stacken och innehåller sitt eget värde. Hit tillhör variabler av typer så som int, bool, enum och struct. Referenstyper skapas också på stacken men med en skillnad; de innehåller inte sitt eget värde direkt på stacken, utan lagrar istället en pekare till något som kallas en heap. En referenstyp är t.ex. en klass, en vektor eller en sträng. Vad är en heap? Varje gång en ny process startas så reserverar .NET en kontinuerlig följd av minne som tilldelas processen. Det är denna följd av minne som kallas för en heap och den används i .NET för att lagra den information som en referenstyp använder. I .NET använder sig heapen av en pekare för att göra en insättning av nya objekt. När heapen skapas pekar denna pekare på den första minnesadressen som heapen använder sig av. När ett nytt objekt skall placeras på heapen stoppas det in på pekarens adress sedan flyttas så att den befinner sig på adressen precis efter objektet. Garbage Collection (GC) Det ställs väldigt högra krav på minneshantering eftersom det är en central del i exekveringen av alla applikationer. Automatiserad minneshantering kräver att vissa av de relevanta aspekterna fungerar på ett optimalt sätt för att inte få negativ inverkan på exekvering:
Microsoft har givetvis implementerat de lösningar som de anser sig vara bäst för .NET-plattformen, samt bedriver kontinuerlig forskning för att kunna förbättra dem ytterligare. Val av objekt att frigöra Det har funnits många lösningar på hur man skall avgöra om ett objekt kan frigöras eller inte. Några av de metoder som använts är att låta utvecklaren ansvara för frigöring av minne eller att använda sig av så kallad referensräkning. |
|
Båda metoderna har fördelar och brister som ligger utanför det som denna artikel behandlar. Givetvis är det så att bristerna är fler än fördelarna, annars skulle inte Microsoft ha valt att implementera en annan form av minneshantering. För att kunna avgöra om ett objekt kan frigöras så måste .NET kunna kontrollera om det används av någon. GC:n avgör om ett objekt kan frigöras eller inte genom att undersöka applikationsrötterna. Varje applikation tilldelas en uppsättning rötter när det startas. Applikationsrötterna består av globala och statiska referenser till objekt, lokala variabler, referensparametrar och CPU-register och kan antingen peka till ett objekt på heapen eller ha ett null-värde. De objekt på heapen som kan nås med hjälp av en rot kommer inte att betraktas som en kandidat för frigöring när minneshantering sker. Tidpunkt för minneshantering En viktig aspekt för automatisk minneshantering är att kunna avgöra när det skall ske. Om hanteringen sker för ofta riskerar man att försämra programmets prestanda och om det sker för sällan ökar risken för att det använda minnet skall växa. För er läsare med kunskaper om hur minneshantering i ett operativsystem fungerar så känner ni till att ökad minneanvändning av ett program kan leda till fler sidallokeringsfel. Det kan i sin tur leda till försämrad prestanda för samtliga processer som exekveras då virtuellt minne måste användas i allt högre grad. Det finns flera faktorer som avgör när .NET skall aktivera GC:n. Den mest grundläggande är den optimeringsmotor som Microsoft har byggt in i .NET och som använder sig av en algoritm för att övervaka minnesanvändningen. Kriterierna för denna algoritm är inte offentliga då det är en av hemligheterna i deras implementation. Som utvecklare har du även möjlighet att själv anropa GC:n och be den frigöra minne från heapen. Det är oftast inte rekommenderat att du utnyttjar denna möjlighet om du inte har väldigt god anledning. Orsaken till detta är att du troligen inte kan fatta ett bättre beslut än vad optimeringsmotorn själv gör, vilket kommer leda till försämrad prestanda av din applikation. När GC:n aktiveras så upphör alla trådar i en applikation att exekvera, vilket är anledningen till att man inte vill att minneshantering skall ske så fort ett objekt skulle kunna frigöras. Frigöringsalgoritm Microsoft valde att implementera GC som genomför sitt arbete i en tvåstegsprocess, en så kallas ”Mark and Sweap” algoritm. Den utgångspunkt man använder sig av är att allt som finns på heapen kan frigöras om det inte kan motbevisas. Första steget kontrollerar om det finns några aktiva referenser mellan objekten och applikationen genom att vandra längs med applikationens rötter. Vid detta tillfälle kontrolleras även indirekta referenser för de objekt som det finns en aktiv referens till. En indirekt referens är en referens till ett objekt som enbart kan nås från ett objekt som är tillgängligt från en av rötterna. Vid andra steget påbörjar GC:n sitt arbeta med att frigöra de objekt som det direkt, eller indirekt, inte kunde hittas någon referens till. Efter det att objekten har frigjorts kommer det att finnas luckor mellan de kvarvarande objekten. För att öka effektiviteten av minnesanvändningen packas de objekt som finns kvar ihop till en kontinuerlig följd. När de två stegen är avslutade har minne frigjorts och GC:n lämnar tillbaka exekveringen till applikationens trådar. Sammanfattning Detta har varit en övergripande introduktion till den minneshantering som .NET Framework använder. Jag kommer vid nästa tillfälle att börja titta in på lite mer komplicerade delar av GC:n och dess funktionalitet. |
1En stack är en datastruktur där information läses i omvänd ordning som den skrevs.