Est-il possible d’avoir une fuite de mémoire dans le code géré? (spécifiquement C # 3.0)

Par exemple, si j’ai une structure de données hiérarchique:

class Node { public List children; } 

et il est peuplé à plusieurs niveaux puis dans l’un des parents vont:

 myNode.children.Clear(); 

qui effacera toutes les références aux enfants immédiats – mais qu’en est-il de tous les petits-enfants, petits-enfants etc. qui ont été référencés par ces enfants immédiats? C # est-il assez intelligent pour savoir qu’ils ne sont plus nécessaires et qu’ils seront ramassés?

J’ai lu à l’aide de la liaison de données WPF sans implémenter l’interface INotifyChanged pouvant causer des memory leaks: http://blogs.msdn.com/b/micmcd/archive/2008/03/07/avoiding-a-wpf-memory-leak-with- databinding-black-magic.aspx , comment est-ce possible dans un environnement géré?

Oui, le ramasse-miettes déterminera que les petits-enfants, etc., sont des ordures. En gros, s’il n’ya aucun moyen d’obtenir un object, il est considéré comme un déchet et peut être collecté.

En ce qui concerne la manière dont les «fuites» de mémoire sont possibles dans le code managé, c’est généralement le cas si vous vous retrouvez avec un object accessible via des références d’object, mais qu’il n’existe aucun moyen de supprimer ces références via une API.

C’est le cas dans le blog que vous avez cité:

Il existe un problème où WPF vérifie la présence d’éléments qui implémentent INotifyProperyChanged. S’il existe une liaison de données à quelque chose qui n’implémente pas cette interface, elle enregistre dans une table globale. Cet enregistrement n’est pas nettoyé, car WPF n’a aucun moyen de vérifier si cet enregistrement de firebase database n’est plus nécessaire.

Donc, il y a cette table globale maintenant des références, et vous n’avez aucun moyen d’indiquer qu’un élément de la table peut être effacé.

Le ramasse-miettes ne collecte que les objects qui ne sont plus utilisés – les memory leaks sont causées par le fait que des objects conservent des références à des objects alors qu’ils ne le devraient pas.

Dans votre cas, si un grand-enfant est utilisé par un autre object, alors .Clear le supprimera de la liste des nœuds, mais le garbage collector ne le collectera pas. Il rassemblera tous les autres petits enfants cependant.

Exemple:

 class Foo { public Node SomeProperty {get; set;} public void SomeFunction(){ var node = new Node { children = new List() }; var childNode = new Node(); var childNode2 = new Node(); node.children.Add(childNode); node.children.Add(childNode2); SomeProperty = childNode2; node.children.Clear(); // childNode will be garbage collected // childNode2 is still used by SomeProperty, // so it won't be garbage collected until SomeProperty or the instance // of Foo is no longer used. } } 

C # s’en moque. C’est le travail des CLR de faire le GC.

Le CPG commence aux objects racine connus (champs statiques, variables locales, …) et parcourt les références jusqu’à ce qu’il ait trouvé tous les objects accessibles. Tous les autres objects peuvent être collectés (à l’exception de certains éléments liés au finaliseur).

Donc, si les références enfants étaient vraiment les seules références à ces objects, les petits-enfants seront également collectés. Mais si un object extérieur vivant a toujours une référence à l’un de vos nœuds, ce nœud et tous les autres objects référencés par lui seront conservés en vie.


Les memory leaks gérées sont causées par des références qui maintiennent les objects en vie.

Par exemple, lors de l’utilisation de la databining, l’interface graphique contient des références aux objects qui les maintiennent en vie.

De même, le fait d’être abonné à un événement conserve l’object associé au gestionnaire d’événements. Donc, parfois, les événements utilisent des références faibles pour éviter ce problème.

En passant, vous pouvez également obtenir des memory leaks dans .net si vous utilisez le mot clé unsafe . Si vous utilisez des pointeurs de la même manière que c ++, etc. et que vous ne veillez pas à ne pas “perdre” une référence de pointeur, le CPG ne pourra pas le collecter.

exemple de bloc dangereux;

 unsafe { int * ptr1, ptr2; ptr1 = &var1; ptr2 = ptr1; *ptr2 = 20; } 

Bien sûr, avec C # particulièrement en avance sur les autres problèmes d’allocation de référence dont tout le monde a parlé, si vous avez une classe qui enveloppe les ressources natives mais qui n’est jamais supprimée (ou si vous perdez la référence), vous pouvez créer une fuite.

Voici un exemple tiré de la classe Image:

 public static void MemLeak() { var src = @"C:\users\devshorts\desktop\bigImage.jpg"; Image image1 = null; foreach (var i in Enumerable.Range(0, 10)) { image1 = Image.FromFile(src); } image1.Dispose(); Console.ReadLine(); } 

Image est jetable, donc comme je la jette à la fin, il ne devrait pas y avoir de fuite, n’est-ce pas? En fait, le fait d’écraser la référence à chaque fois avec une nouvelle image signifie que vous ne pouvez pas disposer des ressources GDI + sous-jacentes que contenait l’ancienne référence d’image. Cela introduira une fuite de mémoire.

Puisque le gc n’appelle pas disposer pour vous et que la classe Image ne remplace pas la méthode Finalize (et n’appelle pas Dispose ici), vous avez donc une fuite.

Oui, vous pouvez avoir tout un graphe d’objects (structure de données massive), mais si rien de tout cela n’est lié ou référencé, il sera récupéré.

Si ce n’est pas le cas, le GC n’a pas été exécuté (vous pouvez essayer GC.Collect() à des fins de diagnostic, mais vous ne devriez pas l’utiliser dans le code de production) ou quelque chose fait référence à une partie de la structure. Par exemple, l’interface utilisateur peut être liée à celle-ci.

Les références circulaires ne posent aucun problème pour le GC dans .NET. Il utilise un algorithme pour déterminer quels objects sont réellement accessibles à partir de certains points d’entrée (par exemple, la méthode principale).

Cependant, ce qui peut causer des fuites théoriques, ce sont les objects qui sont accidentellement référencés par des membres statiques, par exemple.

Votre exemple entre dans la première catégorie et est donc sûr à utiliser.

Il est possible d’avoir une sorte de fuite de mémoire dans .NET.

Si vous avez un object “A” qui s’inscrit à un événement sur un autre object “B”, alors “B” obtient une référence à “A” et continuera de l’être si vous ne désenregistrez pas l’événement lorsque “A” sort de scope. Dans ce cas, “A” ne peut pas être récupéré car il existe toujours une référence active. Il restra en place jusqu’à ce que “B” soit ramassé.

Si vous avez une situation où des objects “A” sont créés et sortent de la scope de façon continue, vous aurez de plus en plus de “A” en mémoire.

Je suggérerais de lire sur la manière dont la récupération de place est gérée dans le monde .net. Essentiellement, cela fonctionne en suivant les références pour trouver tout ce qui pourrait être référencé par un object de niveau supérieur et en libérant tout le rest; cela ne fonctionne pas avec des destructeurs comme le monde C ++, vous pouvez donc être heureux de savoir que les objects gérés “disparaîtront” si leurs parents et leurs grands-parents sont libérés.

Bien entendu, le ramasse-miettes ne connaît que la mémoire gérée, et il est utile d’examiner le modèle IDisposable si vous disposez de ressources non gérées – cela permet une libération déterministe des objects non gérés.

La partie compliquée intervient lorsqu’il est question de ce qui pourrait référencer un object. Elle inclut des éléments moins évidents, tels que les gestionnaires d’événements, d’où provient le problème WPF / INotifyPropertyChanged que vous avez mentionné.

Une fuite de mémoire est fondamentalement un morceau de mémoire qui n’est plus nécessaire au bon comportement d’un programme mais qui ne peut pas être libéré à cause d’une erreur de programmation. Le concept de fuite de mémoire n’a donc rien à voir avec Garbage Collection, C # ou Java.

Prenons cet exemple:

 var list = new List(); Node a1 = new Node(); Node a2 = new Node(); // ... Node an = new Node(); // Populate list list.Add(a1); list.Add(a2); // ... list.Add(an); // use this list DoStuffTo(list); // clear list -- release all elements list.Clear(); // memory leaks from now on 

Notez comment les éléments de la liste sont des memory leaks car ils sont référencés par des variables a1 ... an

Ceci est juste un exemple simple de la raison pour laquelle ce n’est pas seulement C # de s’occuper des memory leaks. Il incombe également au développeur de résoudre ce problème:

 // Clear references a1 = null; a2 = null; // ... an = null; 

Cela indiquera au ramasse-miettes C # que tous ces éléments doivent être collectés.

Oui, des fuites en C # sont causées lorsque les références aux objects ne sont pas correctement supprimées une fois que ces objects ne sont plus nécessaires. Si une référence à un object a été supprimée, le récupérateur de place supprime celui-ci lors de son exécution (il le fait automatiquement en fonction de moments déterminés par un algorithme soigneusement défini. Il est donc préférable de ne pas le faire fonctionner manuellement, vous savez vraiment ce que vous faites!). Mais si la référence à l’object n’est pas correctement supprimée, le récupérateur de place pense toujours que l’application en a besoin, ce qui entraîne une fuite de mémoire. Il est particulièrement courant de voir ce genre de choses se produire avec des gestionnaires d’événements dont on ne s’est pas bien débarrassé. Si un object avec des enfants / petits-enfants a toutes les références supprimées, cet object ainsi que tous ces enfants / petits-enfants seront également supprimés lors de la prochaine exécution du collecteur de place (à moins qu’ils ne soient également référencés ailleurs).

La meilleure chose à faire est d’utiliser un profileur de mémoire, qui vous permettra de voir quels objects contiennent d’autres objects en mémoire (la plupart vous permettent de prendre des instantanés de la mémoire, puis de regarder un type de graphique montrant les références. Si un object existe toujours Vous ne devriez pas, vous pouvez regarder un graphique montrant quelle référence tient cet object en mémoire et l’utiliser pour déterminer où vous auriez dû effacer la référence pour éviter les memory leaks. Il existe quelques profileurs disponibles, mais je trouve la mémoire de fourmis. profileur par porte rouge le plus facile à utiliser http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/ .