Zoeken…


Invoering

In .Net worden objecten gemaakt met new () toegewezen aan de beheerde heap. Deze objecten worden nooit expliciet afgerond door het programma dat ze gebruikt; in plaats daarvan wordt dit proces beheerd door de .Net Garbage Collector.

Sommige van de onderstaande voorbeelden zijn "labcases" om de Garbage Collector aan het werk te laten zien en enkele belangrijke details van zijn gedrag, terwijl andere zich richten op het voorbereiden van klassen voor een juiste afhandeling door de Garbage Collector.

Opmerkingen

De Garbage Collector is gericht op het verlagen van de programmakosten in termen van toegewezen geheugen, maar dit heeft kosten in termen van verwerkingstijd. Om een goed algemeen compromis te bereiken, zijn er een aantal optimalisaties waarmee rekening moet worden gehouden bij het programmeren met de Garbage Collector in gedachten:

  • Als de Collect () -methode expliciet moet worden aangeroepen (wat sowieso niet vaak het geval zou moeten zijn), overweeg dan om de "geoptimaliseerde" modus te gebruiken die het dode object alleen finaliseert wanneer geheugen daadwerkelijk nodig is
  • In plaats van de methode Collect () aan te roepen, kunt u overwegen de methoden AddMemoryPressure () en RemoveMemoryPressure () te gebruiken, die alleen een geheugenverzameling activeren als dat echt nodig is
  • Het is niet gegarandeerd dat een geheugenverzameling alle dode objecten finaliseert; in plaats daarvan beheert de Garbage Collector 3 "generaties", een object dat soms "overleeft" van een generatie naar de volgende
  • Verschillende draadsnijmodellen kunnen van toepassing zijn, afhankelijk van verschillende factoren, waaronder fijnafstemming van de installatie, resulterend in verschillende mate van interferentie tussen de Garbage Collector-thread en de andere applicatiethread (s)

Een eenvoudig voorbeeld van (afval) inzameling

Gegeven de volgende klasse:

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

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

Een programma dat een instantie maakt, zelfs zonder deze te gebruiken:

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

Produceert de volgende uitvoer:

<namespace>.FinalizableObject initialized

Als er niets anders gebeurt, wordt het object pas voltooid als het programma is afgelopen (waardoor alle objecten op de beheerde heap worden vrijgegeven en deze tijdens het proces worden voltooid).

Het is mogelijk om de Garbage Collector te dwingen op een bepaald punt te werken, als volgt:

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

Dat levert het volgende resultaat op:

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

Deze keer, zodra de Garbage Collector werd aangeroepen, werd het ongebruikte (aka "dode") object voltooid en bevrijd van de beheerde hoop.

Levende objecten en dode objecten - de basis

Vuistregel: wanneer afvalinzameling plaatsvindt, zijn "levende objecten" die nog steeds in gebruik zijn, terwijl "dode objecten" die zijn die niet langer worden gebruikt (elke variabele of veld waarnaar wordt verwezen, indien aanwezig, is buiten bereik gegaan voordat de verzameling plaatsvindt) .

In het volgende voorbeeld (voor het gemak zijn FinalizableObject1 en FinalizableObject2 subklassen van FinalizableObject uit het bovenstaande voorbeeld en nemen dus het initialisatie- / finalisatieberichtgedrag over):

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();

De output zal zijn:

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

Op het moment dat de Garbage Collector wordt aangeroepen, is FinalizableObject1 een dood object en wordt het voltooid, terwijl FinalizableObject2 een levend object is en het op de beheerde heap wordt bewaard.

Meerdere dode objecten

Wat als twee (of meerdere) anders dode objecten naar elkaar verwijzen? Dit wordt weergegeven in het onderstaande voorbeeld, ervan uitgaande dat OtherObject een openbare eigenschap van FinalizableObject is:

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();

Dit levert de volgende uitvoer op:

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

De twee objecten worden voltooid en bevrijd van de beheerde heap ondanks dat ze naar elkaar verwijzen (omdat er geen andere verwijzing naar een van hen bestaat uit een daadwerkelijk levend object).

Zwakke referenties

Zwakke verwijzingen zijn ... verwijzingen naar andere objecten (ook wel "doelen" genoemd), maar "zwak" omdat ze niet verhinderen dat die objecten worden verzameld. Met andere woorden, zwakke verwijzingen tellen niet wanneer de Garbage Collector objecten beoordeelt als "live" of "dood".

De volgende code:

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

Produceert de output:

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

Het object wordt vrijgemaakt van de beheerde heap ondanks dat er naar wordt verwezen door de variabele WeakReference (nog steeds in gebruik toen de Garbage-collector werd aangeroepen).

Gevolg # 1: het is op elk moment onveilig om aan te nemen of een WeakReference-doel nog steeds is toegewezen aan de beheerde heap of niet.

Gevolg # 2: wanneer een programma toegang nodig heeft tot het doel van een zwakke referentie, moet in beide gevallen code worden opgegeven, waarbij het doel nog steeds wordt toegewezen of niet. De methode om toegang tot het doel te krijgen is 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
}

De generieke versie van WeakReference is beschikbaar sinds .Net 4.5. Alle frameworkversies bieden een niet-generieke, niet-getypeerde versie die op dezelfde manier is gebouwd en als volgt is gecontroleerd:

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
}

Dispose () versus finalizers

Implementeer de methode Dispose () (en verklaar de bevattende klasse als IDisposable) als een manier om ervoor te zorgen dat geheugenbronnen vrij zijn zodra het object niet langer wordt gebruikt. De "vangst" is dat er geen sterke garantie is dat de methode Dispose () ooit zou worden aangeroepen (in tegenstelling tot finalisten die altijd worden opgeroepen aan het einde van de levensduur van het object).

Eén scenario is een programma dat Dispose () aanroept op objecten die het expliciet maakt:

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
}

Een ander scenario verklaart dat een klasse door het framework wordt geïnstantieerd. In dit geval neemt de nieuwe klasse meestal een basisklasse over, bijvoorbeeld in MVC maakt u een controller-klasse aan als een subklasse van System.Web.Mvc.ControllerBase. Wanneer de basisklasse de IDisposable-interface implementeert, is dit een goede hint dat Dispose () correct zou worden aangeroepen door het framework - maar nogmaals, er is geen sterke garantie.

Dus Dispose () is geen vervanging voor een finalizer; in plaats daarvan moeten de twee voor verschillende doeleinden worden gebruikt:

  • Een finalizer maakt uiteindelijk middelen vrij om geheugenlekken te voorkomen die anders zouden optreden
  • Dispose () maakt middelen vrij (mogelijk dezelfde) zodra deze niet langer nodig zijn, om de druk op de algehele geheugentoewijzing te verlichten.

Correcte verwijdering en afwerking van objecten

Omdat Dispose () en finalizers voor verschillende doeleinden zijn bedoeld, moet een klasse die externe geheugenzware bronnen beheert, beide implementeren. Het gevolg is dat de klasse zo wordt geschreven dat deze goed twee mogelijke scenario's verwerkt:

  • Wanneer alleen de finalizer wordt aangeroepen
  • Wanneer Dispose () eerst wordt aangeroepen en later wordt ook de finalizer aangeroepen

Een oplossing is de opschoningscode zodanig schrijven dat het een of twee keer uitvoeren hetzelfde resultaat oplevert als het slechts één keer uitvoeren. Haalbaarheid hangt af van de aard van de opruiming, bijvoorbeeld:

  • Het sluiten van een reeds gesloten databaseverbinding zou waarschijnlijk geen effect hebben, dus het werkt
  • Het bijwerken van een aantal "gebruikstellingen" is gevaarlijk en zou een verkeerd resultaat opleveren wanneer het tweemaal wordt aangeroepen in plaats van één keer.

Een veiligere oplossing is ervoor zorgen dat de opschooncode één keer en slechts één keer wordt aangeroepen, ongeacht de externe context. Dit kan op de "klassieke manier" worden bereikt met behulp van een speciale vlag:

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;
        }
    }
}

Als alternatief biedt de Garbage Collector een specifieke methode SuppressFinalize () waarmee de finalizer kan worden overgeslagen nadat Dispose is aangeroepen:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow