Sök…


Introduktion

I .Net allokeras objekt som skapats med nya () på den hanterade högen. Dessa objekt slutförs aldrig uttryckligen av programmet som använder dem; istället styrs denna process av. Net Garbage Collector.

Några av exemplen nedan är "labbfall" för att visa soporna i arbetet och vissa betydande detaljer om dess beteende, medan andra fokuserar på hur man förbereder klasser för korrekt hantering av sopor.

Anmärkningar

Garbage Collector syftar till att sänka programkostnaden i form av tilldelat minne, men att göra det har en kostnad när det gäller behandlingstid. För att uppnå en bra övergripande kompromiss finns det ett antal optimeringar som bör beaktas vid programmering med Garbage Collector i åtanke:

  • Om metoden Collect () ska uttryckligen åberopas (vilket inte ofta borde vara fallet), överväg att använda det "optimerade" läget som slutför dött objekt endast när minnet faktiskt behövs
  • Istället för att åberopa metoden Collect () kan du överväga att använda metoderna AddMemoryPressure () och RemoveMemoryPressure (), som utlöser en minnessamling endast om det faktiskt behövs
  • En minnessamling garanteras inte att slutföra alla döda föremål; istället hanterar Garbage Collector 3 "generationer", ett objekt som ibland "överlever" från en generation till nästa
  • Flera gängmodeller kan tillämpas, beroende på olika faktorer inklusive fininställning, vilket resulterar i olika grader av störningar mellan Garbage Collector-tråden och den andra applikationstråden

Ett grundläggande exempel på (sopor) insamling

Följande klass:

public class FinalizableObject 
{
    public FinalizableObject()
    {
        Console.WriteLine("Instance initialized");
    }

    ~FinalizableObject()
    {
        Console.WriteLine("Instance finalized");
    }
}

Ett program som skapar en instans, även utan att använda den:

new FinalizableObject(); // Object instantiated, ready to be used

Tillverkar följande utgång:

<namespace>.FinalizableObject initialized

Om inget annat händer slutförs objektet inte förrän programmet slutar (vilket frigör alla objekt på den hanterade högen, slutför dessa i processen).

Det är möjligt att tvinga Garbage Collector att köra vid en given punkt, enligt följande:

new FinalizableObject(); // Object instantiated, ready to be used
GC.Collect();

Vilket ger följande resultat:

<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized

Denna gång, så snart Garbage Collector anropades, slutfördes det oanvända (alias "döda") objektet och befriades från den hanterade högen.

Levande föremål och döda föremål - grunderna

Tumregel: när soporuppsamling inträffar är "levande föremål" de som fortfarande används, medan "döda föremål" är de som inte längre används (någon variabel eller fält som hänvisar till dem, om någon, har gått ut ur räckvidden innan samlingen inträffar) .

I följande exempel (för enkelhets skull är FinalizableObject1 och FinalizableObject2 underklasser av FinalizableObject från exemplet ovan och ärver sålunda beteendet med initialiserings- / finaliseringsmeddelandet):

var obj1 = new FinalizableObject1(); // Finalizable1 instance allocated here
var obj2 = new FinalizableObject2(); // Finalizable2 instance allocated here
obj1 = null; // No more references to the Finalizable1 instance 
GC.Collect();

Utgången kommer att vara:

<namespace>.FinalizableObject1 initialized
<namespace>.FinalizableObject2 initialized
<namespace>.FinalizableObject1 finalized

Vid den tidpunkt då Garbage Collector anropas är FinalizableObject1 ett dött objekt och blir slutbehandlat, medan FinalizableObject2 är ett liveobjekt och det hålls på den hanterade högen.

Flera döda föremål

Tänk om två (eller flera) i övrigt döda föremål refererar till varandra? Detta visas i exemplet nedan och antar att OtherObject är en offentlig egendom till FinalizableObject:

var obj1 = new FinalizableObject1(); 
var obj2 = new FinalizableObject2();
obj1.OtherObject = obj2;
obj2.OtherObject = obj1;
obj1 = null; // Program no longer references Finalizable1 instance
obj2 = null; // Program no longer references Finalizable2 instance
// But the two objects still reference each other
GC.Collect();

Detta ger följande utgång:

<namespace>.FinalizedObject1 initialized
<namespace>.FinalizedObject2 initialized
<namespace>.FinalizedObject1 finalized
<namespace>.FinalizedObject2 finalized

De två föremålen slutförs och befrias från den hanterade högen trots att de hänvisar till varandra (eftersom det inte finns någon annan referens till någon av dem från ett faktiskt levande objekt).

Svaga referenser

Svaga referenser är ... referenser till andra objekt (alias "mål"), men "svaga" eftersom de inte förhindrar att dessa objekt samlas in i skräp. Med andra ord räknas inte svaga referenser när Garbage Collector utvärderar objekt som "levande" eller "döda".

Följande kod:

var weak = new WeakReference<FinalizableObject>(new FinalizableObject());
GC.Collect();

Ger produktionen:

<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized

Objektet befrias från den hanterade högen trots att den hänvisas till av WeakReference-variabeln (fortfarande inom räckvidd när avfallssamlaren åberopades).

Konsekvens nr 1: när som helst är det osäkert att anta om ett WeakReference-mål fortfarande är tilldelat på den hanterade högen eller inte.

Konsekvens nr 2: när ett program behöver åtkomst till målet för en svagreferens bör kod anges för båda fallen, varvid målet fortfarande tilldelas eller inte. Metoden för att komma åt målet är TryGetTarget:

var target = new object(); // Any object will do as target
var weak = new WeakReference<object>(target); // Create weak reference
target = null; // Drop strong reference to the target

// ... Many things may happen in-between

// Check whether the target is still available
if(weak.TryGetTarget(out target))
{
    // Use re-initialized target variable
    // To do whatever the target is needed for
}
else
{
    // Do something when there is no more target object
    // The target variable value should not be used here
}

Den generiska versionen av WeakReference är tillgänglig sedan .Net 4.5. Alla ramversioner tillhandahåller en icke-generisk, otypad version som är byggd på samma sätt och kontrolleras enligt följande:

var target = new object(); // Any object will do as target
var weak = new WeakReference(target); // Create weak reference
target = null; // Drop strong reference to the target

// ... Many things may happen in-between

// Check whether the target is still available
if (weak.IsAlive)
{
    target = weak.Target;

    // Use re-initialized target variable
    // To do whatever the target is needed for
}
else
{
    // Do something when there is no more target object
    // The target variable value should not be used here
}

Kassera () kontra slutbehandlare

Implement Dispose () -metoden (och förklara den innehållande klassen som IDisponerbar) som ett medel för att säkerställa att alla minnestunga resurser frigörs så snart objektet inte längre används. "Fångsten" är att det inte finns någon stark garanti för att Dispose () -metoden någonsin skulle åberopas (till skillnad från slutförare som alltid åberopas i slutet av objektets liv).

Ett scenario är ett program som kallar Dispose () på objekt som det uttryckligen skapar:

private void SomeFunction()
{
    // Initialize an object that uses heavy external resources
    var disposableObject = new ClassThatImplementsIDisposable();

    // ... Use that object

    // Dispose as soon as no longer used
    disposableObject.Dispose();

    // ... Do other stuff 

    // The disposableObject variable gets out of scope here
    // The object will be finalized later on (no guarantee when)
    // But it no longer holds to the heavy external resource after it was disposed
}

Ett annat scenario är att förklara att en klass ska instanseras av ramverket. I det här fallet ärver den nya klassen vanligtvis en basklass, till exempel i MVC skapar man en kontrollklass som en underklass för System.Web.Mvc.ControllerBase. När basklassen implementerar IDisposable gränssnitt är detta en bra antydan att Dispose () skulle åberopas ordentligt av ramverket - men återigen finns det ingen stark garanti.

Således är Dispose () inte ett substitut för en finaliserare; istället bör de två användas för olika ändamål:

  • En finaliserare frigör så småningom resurser för att undvika minnesläckor som skulle inträffa annars
  • Kassera () frigör resurser (kanske samma) så snart dessa inte längre behövs för att underlätta trycket på den övergripande minnesallokeringen.

Korrekt bortskaffande och slutförande av objekt

Eftersom Dispose () och slutbehandlare är inriktade på olika syften, bör en klass som hanterar externa minnestunga resurser implementera båda. Konsekvensen är att skriva klassen så att den hanterar två möjliga scenarier:

  • När endast finaliseraren åberopas
  • När Dispose () åberopas först och senare åberopas även finaliseraren

En lösning är att skriva rensningskoden på ett sådant sätt att köra den en eller två gånger skulle ge samma resultat som att köra den bara en gång. Genomförbarhet beror på saneringen, till exempel:

  • Att stänga en redan stängd databasanslutning har förmodligen ingen effekt så det fungerar
  • Att uppdatera en del av "användningsantalet" är farligt och skulle ge fel resultat när du ringde två gånger istället för en gång.

En säkrare lösning är att säkerställa genom design att rensningskoden kallas en gång och endast en gång oavsett den externa kontexten. Detta kan uppnås på det "klassiska sättet" med hjälp av en dedikerad flagga:

public class DisposableFinalizable1: IDisposable
{
    private bool disposed = false;

    ~DisposableFinalizable1() { Cleanup(); }

    public void Dispose() { Cleanup(); }

    private void Cleanup()
    {
        if(!disposed)
        {
            // Actual code to release resources gets here, then
            disposed = true;
        }
    }
}

Alternativt tillhandahåller Garbage Collector en specifik metod SuppressFinalize () som gör det möjligt att hoppa över slutbehandlaren efter att Avfall har anropats:

public class DisposableFinalizable2 : IDisposable
{
    ~DisposableFinalizable2() { Cleanup(); }

    public void Dispose()
    {
        Cleanup();
        GC.SuppressFinalize(this);
    }

    private void Cleanup()
    {
        // Actual code to release resources gets here
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow