33

J'essaie de récupérer une fuite de mémoire dans une application Windows Forms. Je regarde maintenant un formulaire qui contient plusieurs formes incorporées. Ce qui m'inquiète, c'est que l'enfant forme, dans son constructeur, une référence au formulaire parent, et le garde dans un champ privé. Donc, il me semble que lorsque vient le temps de collecte des ordures:Les références circulaires entraînent-elles une fuite de mémoire?

Parent a une référence à la forme de l'enfant, par l'intermédiaire de la collection de contrôles (y est intégré sous forme de l'enfant). La forme enfant n'est pas GC'd.

forme d'enfant a une référence à la forme mère, par l'intermédiaire du champ de membre privé. Le formulaire parent n'est pas GC'd.

Est-ce une bonne compréhension de la façon dont le garbage collector évaluera la situation? Un moyen de le «prouver» à des fins de test?

Répondre

36

Bonne question!

Non, les deux formes seront (peut être) GC'd parce que le GC ne regarde pas directement des références dans d'autres références. Il ne recherche que les références "Root" ... Cela inclut les variables de référence sur la pile, (la variable est sur la pile, l'objet réel est bien sur le tas), les variables de référence dans les registres CPU et les variables de référence champs statiques dans les classes ...

Toutes les autres variables de référence sont seulement accédées (et GC'd) si elles sont référencées dans une propriété de l'un des objets de référence "racine" trouvés par le processus ci-dessus ... (ou dans un objet référencé par une référence dans un objet racine, etc ...)

Donc seulement si l'une des formes est référencée ailleurs dans une référence "racine" - Alors les deux formes seront à l'abri du GC. La seule façon dont je peux penser à le "prouver", (sans utiliser les utilitaires de trace de mémoire) serait de créer quelques centaines de milliers de ces formes, dans une boucle à l'intérieur d'une méthode, puis, alors que dans la méthode, regardez l'empreinte mémoire de l'application, puis quittez la méthode, appelez le CPG et observez à nouveau l'empreinte.

+2

Ou tout simplement allouer un tampon massif à l'intérieur de chaque forme. – Gusdor

5

Si le parent et l'enfant ne sont pas référencés, mais ils référence seulement eachother, ils obtiennent GCed.

Obtenir un profileur de mémoire pour vérifier vraiment votre application et répondre à toutes vos questions. Je peux recommander http://memprofiler.com/

0

Le GC peut traiter correctement avec des références circulaires et si ces références étaient les seules choses qui maintiennent la forme en vie, elles seraient collectées.
J'ai eu beaucoup de problèmes avec .net ne récupérant pas la mémoire des formulaires. En 1.1, il y avait quelques bugs au niveau de menuitem (je pense) qui signifiaient qu'ils n'étaient pas éliminés et pouvaient laisser des traces de mémoire. Dans ce cas, l'ajout d'un appel explicite pour disposer et effacer la variable membre dans la méthode Dispose du formulaire a résolu le problème. Nous avons constaté que cela permettait également de récupérer de la mémoire pour certains des autres types de contrôle.
J'ai aussi passé beaucoup de temps avec le profileur CLR pour savoir pourquoi les formulaires n'étaient pas collectés. Pour autant que je sache, les références étaient conservées par le cadre. Un par type de formulaire. Donc, si vous créez 100 instances de Form1, puis les fermez toutes, seulement 99 seront récupérées correctement. Je n'ai trouvé aucun moyen de guérir cela.
Notre application a depuis déplacé vers .net 2 et cela semble être beaucoup mieux. Notre mémoire d'application augmente encore quand nous ouvrons le premier formulaire et ne redescend pas quand il est fermé mais je crois que ceci est dû au code JIT et aux bibliothèques de contrôle supplémentaires qui sont chargées.
J'ai également trouvé que bien que le GC puisse traiter des références circulaires, il semble avoir des problèmes (parfois) avec des références de gestionnaire d'événements circulaires.IE object1 références object2 et object1 a une méthode qui gère et événement de object2. J'ai trouvé des circonstances où cela n'a pas libéré les objets quand je m'attendais mais je n'ai jamais pu le reproduire dans un cas de test.

16

Comme d'autres l'ont déjà dit, GC n'a aucun problème avec les références circulaires. Je voudrais juste ajouter, qu'un endroit commun pour fuir la mémoire dans. NET sont des gestionnaires d'événement. Si l'un de vos formulaires a un gestionnaire d'événements attaché à un autre objet qui est "vivant", alors il y a une référence à votre formulaire et le formulaire ne recevra pas GC'd.

+2

Une réponse qui décrit votre exemple: http://stackoverflow.com/a/12133788/6345 –

12

La collecte des ordures fonctionne en suivant les racines de l'application. Les racines d'application sont des emplacements de stockage qui contiennent des références aux objets sur le tas géré (ou null). Dans .NET, les racines sont

  1. Les références à des objets globaux
  2. Les références à des objets statiques
  3. Les références aux champs statiques
  4. Références sur la pile d'objets locaux
  5. Références sur la pile à l'objet de paramètres passé aux méthodes
  6. Références aux objets en attente de finalisation
  7. Références dans les registres de la CPU aux objets gérés ap

La liste des racines actives est maintenue par le CLR. Le garbage collector fonctionne en regardant les objets sur le tas géré et en voyant ceux qui sont encore accessibles par l'application, c'est-à-dire accessibles via une racine d'application. Un tel objet est considéré comme enraciné. Supposons maintenant que vous avez un formulaire parent qui contient des références aux formulaires enfants et que ces formulaires enfants contiennent des références au formulaire parent. En outre, supposons que l'application ne contienne plus de références au parent ou aux formulaires enfants. Ensuite, pour les besoins du garbage collector, ces objets gérés ne sont plus enracinés et seront collectés lors de la prochaine récupération d'un garbage.

+0

@Jason, qu'entendez-vous par un "paramètre d'objet"? Et je crois que l'emplacement de la référence est le déterminant critique ... Si sur la pile, ou un membre statique d'une classe, ou dans un registre CPU, alors c'est une référence racine. ... sinon, pas. (sauf pour la queue freachable, - un autre sujet) –

2

Je voudrais faire écho à la remarque de Vilx sur les événements, et de recommander un modèle de conception qui aide à y remédier.

Disons que vous avez un type qui est une source d'événement, .: par exemple

interface IEventSource 
{ 
    event EventHandler SomethingHappened; 
} 

Voici un extrait d'une classe qui gère les événements des instances de ce type. L'idée est que chaque fois que vous affectez une nouvelle instance à la propriété, vous devez d'abord vous désabonner de toute affectation précédente, puis vous abonner à la nouvelle instance. Les vérifications nuls garantissent des comportements de limites correctes, et plus précisément, simplifient la mise au rebut: tout ce que vous faites est nul la propriété.

Ce qui amène à la mise au rebut. Toute classe abonnée à des événements doit implémenter l'interface IDisposable car les événements sont des ressources gérées. (N.B. je sauté une bonne mise en œuvre du modèle Dispose dans l'exemple pour des raisons de concision, mais vous voyez l'idée.)

class MyClass : IDisposable 
{ 
    IEventSource m_EventSource; 
    public IEventSource EventSource 
    { 
     get { return m_EventSource; } 
     set 
     { 
      if(null != m_EventSource) 
      { 
       m_EventSource -= HandleSomethingHappened; 
      } 
      m_EventSource = value; 
      if(null != m_EventSource) 
      { 
       m_EventSource += HandleSomethingHappened; 
      } 
     } 
    } 

    public Dispose() 
    { 
     EventSource = null; 
    } 

    // ... 
}