web-dev-qa-db-fra.com

Quelle est la bonne façon de libérer de la mémoire en C #

J'ai un temporisateur en C # qui exécute du code à l'intérieur de sa méthode. Dans le code, j'utilise plusieurs objets temporaires.

  1. Si j'ai quelque chose comme Foo o = new Foo(); à l'intérieur de la méthode, cela signifie-t-il que chaque fois que le chronomètre se déclenche, je crée un nouvel objet et une nouvelle référence à cet objet?

  2. Si j'ai string foo = null Et que je mets juste quelque chose de temporel dans foo, est-ce la même chose que ci-dessus?

  3. Le garbage collector supprime-t-il jamais l'objet et la ou les références sont continuellement créées et restent en mémoire?

  4. Si je déclare simplement Foo o; Sans le pointer vers une instance, cela n'est-il pas supprimé à la fin de la méthode?

  5. Si je veux m'assurer que tout est supprimé, quelle est la meilleure façon de le faire:

    • avec l'instruction using dans la méthode
    • en appelant la méthode dispose à la fin
    • en plaçant Foo o; en dehors de la méthode du temporisateur et en faisant simplement l'affectation o = new Foo() à l'intérieur, le pointeur vers l'objet est supprimé à la fin de la méthode, le garbage collector supprimera l'objet.
32
user579674

1.Si j'ai quelque chose comme Foo o = new Foo (); à l'intérieur de la méthode, cela signifie-t-il que chaque fois que le chronomètre se déclenche, je crée un nouvel objet et une nouvelle référence à cet objet?

Oui.

2.Si j'ai la chaîne foo = null et que je mets juste quelque chose de temporel dans foo, est-ce la même chose que ci-dessus?

Si vous demandez si le comportement est le même, alors oui.

3. Le garbage collector supprime-t-il jamais l'objet et la ou les références sont continuellement créées et restent en mémoire?

La mémoire utilisée par ces objets est très certainement collectée une fois que les références sont considérées comme inutilisées.

4.Si je viens de déclarer Foo o; et ne pas le pointer vers une instance, n'est-ce pas disposé à la fin de la méthode?

Non, car aucun objet n'a été créé, il n'y a donc aucun objet à collecter (disposer n'est pas le bon mot).

5.Si je veux m'assurer que tout est supprimé, quelle est la meilleure façon de le faire

Si la classe de l'objet implémente IDisposable alors vous voulez certainement appeler avec avidité Dispose dès que possible. Le mot clé using rend cela plus facile car il appelle automatiquement Dispose de manière sûre.

À part cela, il n'y a vraiment rien d'autre à faire que d'arrêter d'utiliser l'objet. Si la référence est une variable locale, lorsqu'elle sortira du champ d'application, elle pourra être collectée.1 S'il s'agit d'une variable de niveau classe, vous devrez peut-être lui affecter null pour la rendre éligible avant que la classe contenante ne soit éligible.


1C'est techniquement incorrect (ou au moins un peu trompeur). Un objet peut être éligible à la collecte bien avant qu'il ne soit hors de portée. Le CLR est optimisé pour collecter de la mémoire lorsqu'il détecte qu'une référence n'est plus utilisée. Dans les cas extrêmes, le CLR peut collecter un objet même si l'une de ses méthodes est toujours en cours d'exécution!

Mise à jour:

Voici un exemple qui démontre que le GC collectera des objets même s'ils peuvent encore être dans le champ d'application. Vous devez compiler une version Release et l'exécuter en dehors du débogueur.

static void Main(string[] args)
{
    Console.WriteLine("Before allocation");
    var bo = new BigObject();
    Console.WriteLine("After allocation");
    bo.SomeMethod();
    Console.ReadLine();
    // The object is technically in-scope here which means it must still be rooted.
}

private class BigObject
{
    private byte[] LotsOfMemory = new byte[Int32.MaxValue / 4];

    public BigObject()
    {
        Console.WriteLine("BigObject()");
    }

    ~BigObject()
    {
        Console.WriteLine("~BigObject()");
    }

    public void SomeMethod()
    {
        Console.WriteLine("Begin SomeMethod");
        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine("End SomeMethod");
    }
}

Sur ma machine, le finaliseur est exécuté pendant que SomeMethod est toujours en cours d'exécution!

33
Brian Gideon

Le garbage collector .NET s'occupe de tout cela pour vous.

Il est capable de déterminer quand les objets ne sont plus référencés et libérera (éventuellement) la mémoire qui leur avait été allouée.

15
Yuck

Les objets sont éligibles pour la collecte des ordures une fois qu'ils sortir du cadre devenir inaccessible (merci ben!). La mémoire ne sera libérée que si le garbage collector estime que vous manquez de mémoire.

Pour les ressources gérées, le garbage collector saura quand cela se produit et vous n'avez rien à faire.

Pour les ressources non gérées (telles que les connexions aux bases de données ou les fichiers ouverts), le garbage collector n'a aucun moyen de savoir combien de mémoire il consomme, et c'est pourquoi vous devez les libérer manuellement (en utilisant dispose, ou mieux encore le bloc using)

Si les objets ne sont pas libérés, soit vous avez beaucoup de mémoire et il n'y a pas besoin, soit vous conservez une référence à eux dans votre application, et par conséquent le garbage collector ne les libérera pas (au cas où vous utilisez réellement cette référence, vous entretenu)

5
Martin Booth

Voici un bref aperçu:

  • Une fois les références disparues, votre objet probablement sera récupéré.
  • Vous ne pouvez compter que sur une collecte statistique qui maintient la taille de votre tas normale à condition que toutes les références aux ordures aient vraiment disparu. En d'autres termes, il n'y a aucune garantie qu'un objet spécifique sera jamais récupéré.
    • Il s'ensuit que votre finaliseur ne sera également jamais garanti d'être appelé. Évitez les finaliseurs.
  • Deux sources courantes de fuites:
    • Les gestionnaires d'événements et les délégués sont des références. Si vous vous abonnez à un événement d'un objet, vous y faites référence. Si vous avez un délégué à la méthode d'un objet, vous le référencez.
    • Par définition, les ressources non gérées ne sont pas collectées automatiquement. C'est à cela que sert le modèle IDisposable.
  • Enfin, si vous voulez une référence qui n'empêche pas l'objet d'être collecté, regardez dans WeakReference.

Une dernière chose: si vous déclarez Foo foo; sans l'attribuer, vous n'avez pas à vous inquiéter - rien ne fuit. Si Foo est un type de référence, rien n'a été créé. Si Foo est un type de valeur, il est alloué sur la pile et sera donc automatiquement nettoyé.

2
Kevin Hsu
  1. Oui
  2. Qu'entendez-vous par la même chose? Il sera réexécuté à chaque exécution de la méthode.
  3. Oui, le garbage collector .Net utilise un algorithme qui commence par toutes les variables globales/de portée, les parcourt en suivant toute référence qu'il trouve récursivement et supprime tout objet en mémoire jugé inaccessible. voir ici pour plus de détails sur Garbage Collection
  4. Oui, la mémoire de toutes les variables déclarées dans une méthode est libérée lorsque la méthode se termine car elles sont toutes inaccessibles. De plus, toutes les variables déclarées mais jamais utilisées seront optimisées par le compilateur, donc en réalité votre variable Foo ne prendra jamais de mémoire.
  5. l'instruction using appelle simplement dispose sur un objet IDisposable à sa sortie, c'est donc l'équivalent de votre deuxième puce. Les deux indiqueront que vous avez terminé avec l'objet et diront au GC que vous êtes prêt à le lâcher. Le remplacement de la seule référence à l'objet aura un effet similaire.
2
Gordon Gustafson

Répondons à vos questions une par une.

  1. Oui, vous créez un nouvel objet chaque fois que cette instruction est exécutée, cependant, elle devient "hors de portée" lorsque vous quittez la méthode et elle est éligible pour le garbage collection.
  2. Eh bien, ce serait la même chose que # 1, sauf que vous avez utilisé un type de chaîne. Un type de chaîne est immuable et vous obtenez un nouvel objet chaque fois que vous effectuez une affectation.
  3. Oui, le garbage collector collecte les objets hors de portée, sauf si vous affectez l'objet à une variable avec une grande portée telle qu'une variable de classe.
  4. Oui.
  5. L'instruction using ne s'applique qu'aux objets qui implémentent l'interface IDisposable. Si tel est le cas, l'utilisation de tous les moyens est préférable pour les objets dans la portée d'une méthode. Ne mettez pas Foo o à une plus grande portée, sauf si vous avez une bonne raison de le faire. Il est préférable de limiter la portée de toute variable à la plus petite portée qui a du sens.
2
J Edward Ellis

Le ramasse-miettes viendra et nettoiera tout ce qui n'y fait plus référence. À moins que vous ne disposiez de ressources non gérées dans Foo, appeler Dispose ou utiliser une instruction using dessus ne vous aidera pas vraiment.

Je suis assez sûr que cela s'applique, car il était toujours en C #. Mais, j'ai suivi un cours de conception de jeu en utilisant XNA et nous avons passé un peu de temps à parler du garbage collector pour C #. La collecte des ordures est coûteuse, car vous devez vérifier si vous avez des références à l'objet que vous souhaitez collecter. Ainsi, le GC essaie de reporter cela aussi longtemps que possible. Donc, tant que vous ne manquiez pas de mémoire physique lorsque votre programme est passé à 700 Mo, il se peut que le GC soit paresseux et ne s'en préoccupe pas encore.

Mais, si vous utilisez simplement Foo o En dehors de la boucle et créez une o = new Foo() à chaque fois, tout devrait fonctionner correctement.

1
fire.eagle

Comme le souligne Brian, le GC peut collecter tout ce qui est inaccessible, y compris les objets qui sont toujours dans la portée et même pendant que les méthodes d'instance de ces objets sont toujours en cours d'exécution. considérez le code suivant:

class foo
{
    static int liveFooInstances;

    public foo()
    {
        Interlocked.Increment(ref foo.liveFooInstances);
    }

    public void TestMethod()
    {
        Console.WriteLine("entering method");
        while (Interlocked.CompareExchange(ref foo.liveFooInstances, 1, 1) == 1)
        {
            Console.WriteLine("running GC.Collect");
            GC.Collect();
            GC.WaitForPendingFinalizers();
        }
        Console.WriteLine("exiting method");
    }

    ~foo()
    {
        Console.WriteLine("in ~foo");
        Interlocked.Decrement(ref foo.liveFooInstances);
    }

}

class Program
{

    static void Main(string[] args)
    {
        foo aFoo = new foo();
        aFoo.TestMethod();
        //Console.WriteLine(aFoo.ToString()); // if this line is uncommented TestMethod will never return
    }
}

s'il est exécuté avec une version de débogage, avec le débogueur attaché ou avec la ligne spécifiée, TestMethod ne sera jamais renvoyé. Mais fonctionner sans un débogueur attaché TestMethod reviendra.

1
Yaur