Pourquoi .NET ne peut pas avoir de memory leaks?

Ignorant le code non sécurisé, .NET ne peut pas présenter de fuite de mémoire. Je l’ai lu sans cesse de nombreux experts et je le crois. Cependant, je ne comprends pas pourquoi.

Je pense que le framework lui-même est écrit en C ++ et que C ++ est susceptible de fuir de mémoire.

  • Le cadre sous-jacent est-il si bien écrit qu’il ne peut absolument pas y avoir de fuite de mémoire interne?
  • Y a-t-il quelque chose dans le code du framework qui gère lui-même et même soigne ses propres memory leaks?
  • La réponse est-elle autre chose que je n’ai pas envisagée?

Il y a déjà quelques bonnes réponses ici, mais je veux aborder un point supplémentaire. Revenons très attentivement à votre question spécifique:


Je pense que le framework lui-même est écrit en C ++ et que C ++ est susceptible de fuir de mémoire.

  • Le cadre sous-jacent est-il si bien écrit qu’il ne peut absolument pas y avoir de fuite de mémoire interne?
  • Y a-t-il quelque chose dans le code du framework qui gère lui-même et même soigne ses propres memory leaks?
  • La réponse est-elle autre chose que je n’ai pas envisagée?

La clé ici est de faire la distinction entre votre code et leur code. Le framework .Net (et Java, Go, python et autres langages récupérés) promettent que si vous vous fiez à leur code, votre code ne laissera aucune fuite de mémoire … du moins au sens traditionnel. Vous pourriez vous trouver dans des situations où certains objects ne sont pas libérés comme prévu, mais ces cas sont subtilement différents des memory leaks traditionnelles, car les objects sont toujours accessibles dans votre programme.

Vous êtes confus car vous comprenez bien que ce n’est pas la même chose que de dire que tout programme que vous créez ne peut pas avoir de fuite de mémoire traditionnelle. Il pourrait toujours y avoir un bogue dans leur code qui fuit de la mémoire.

Alors maintenant, vous devez vous demander: préféreriez-vous faire confiance à votre code ou à leur code? Gardez à l’esprit que leur code n’est pas seulement testé par les développeurs d’origine (comme le vôtre, non?), Il est également renforcé par une utilisation quotidienne par des milliers (peut-être des millions) d’autres programmeurs comme vous. Tout problème de fuite de mémoire important serait parmi les premiers éléments identifiés et corrigés. Encore une fois, je ne dis pas que ce n’est pas possible. C’est juste que c’est généralement une meilleure idée de faire confiance à leur code que c’est le vôtre … du moins à cet égard.

Par conséquent, la bonne réponse est que c’est une variante de votre première suggestion:

Le cadre sous-jacent est-il si bien écrit qu’il ne peut absolument pas y avoir de fuite de mémoire interne?

Ce n’est pas qu’il n’y ait pas de possibilité, mais c’est beaucoup plus sûr que de le gérer vous-même. Je ne suis certainement pas au courant de fuites connues dans le cadre.

.NET peut avoir des memory leaks.

La plupart du temps, les gens se réfèrent au Garbage Collector, qui décide quand un object (ou un cycle d’object entier) peut être éliminé. Cela évite les memory leaks de style c et c ++ classiques, ce qui signifie allouer de la mémoire sans la libérer ultérieurement.

Cependant, bien des fois, les programmeurs ne réalisent pas que les objects ont toujours des références en suspens et ne reçoivent pas de déchets, provoquant une fuite de mémoire.

C’est généralement le cas lorsque des événements sont enregistrés (avec += ) mais non enregistrés ultérieurement, mais également lors de l’access à du code non géré (à l’aide de pInvokes ou d’objects utilisant des ressources système sous-jacentes, telles que les connexions au système de fichiers ou à la firebase database). les ressources.

Après avoir examiné la documentation de Microsoft, en particulier « Identification des memory leaks dans le CLR », Microsoft déclare que tant que vous n’implémentez pas de code dangereux dans votre application, il est impossible qu’une fuite de mémoire se produise.

Maintenant, ils soulignent également le concept d’une fuite de mémoire perçue ou, comme cela a été souligné dans les commentaires, une “fuite de ressource”, qui est l’utilisation d’un object qui a des références persistantes et n’est pas éliminé correctement. Cela peut se produire avec les objects IO, les DataSets, les éléments GUI, etc. Ils sont ce que je qualifierais généralement de “fuite de mémoire” lorsque je travaille avec .NET, mais ils ne sont pas des fuites au sens traditionnel.

En raison de la récupération de la mémoire, vous ne pouvez pas avoir de memory leaks régulières (à part des cas particuliers tels que le code non sécurisé et le P / Invoke). Cependant, vous pouvez certainement conserver involontairement une référence pour toujours, ce qui fuit efficacement la mémoire.

modifier

Le meilleur exemple que j’ai vu jusqu’à présent d’une véritable fuite est le gestionnaire d’événements + = erreur.

modifier

Voir ci-dessous pour une explication de l’erreur et des conditions dans lesquelles elle est qualifiée de véritable fuite, par opposition à une fuite quasi authentique.

Voici un exemple de fuite de mémoire dans .NET, qui n’implique pas unsafe / pinvoke et n’implique même pas de gestionnaires d’événements.

Supposons que vous écrivez un service d’arrière-plan qui reçoit une série de messages sur un réseau et les traite. Donc, vous créez une classe pour les tenir.

 class Message { public Message(int id, ssortingng text) { MessageId = id; Text = text; } public int MessageId { get; private set; } public ssortingng Text { get; private set; } } 

OK, jusqu’ici tout va bien. Plus tard, vous réalisez que certaines exigences du système pourraient être plus faciles si vous aviez une référence au message précédent disponible lors du traitement. Il pourrait y avoir un certain nombre de raisons pour le vouloir.

Donc, vous ajoutez une nouvelle propriété …

 class Message { ... public Message PreviousMessage { get; private set; } ... } 

Et vous écrivez le code pour le définir. Et bien sûr, quelque part dans la boucle principale, vous devez avoir une variable pour suivre le dernier message:

  Message lastMessageReceived; 

Ensuite, vous découvrez quelques jours plus tard que votre service a bombardé, car il a rempli toute la mémoire disponible avec une longue chaîne de messages obsolètes.

Voici d’autres memory leaks que ce gars a trouvé en utilisant ANTS .NET Profiler: http://www.simple-talk.com/dotnet/.net-tools/tracing-memory-leaks-in-.net-applications-with-ants -profiler /

Je suppose qu’il est possible d’écrire un logiciel, par exemple l’environnement d’exécution .NET (le CLR), qui ne fuit pas la mémoire si l’on est suffisamment prudent. Mais comme Microsoft publie de temps en temps des mises à jour du framework .NET via Windows Update, je suis à peu près certain qu’il ya des bogues occasionnels, même dans le CLR.

Tous les logiciels peuvent perdre de la mémoire.

Mais comme d’autres l’ont déjà souligné, il existe d’autres types de memory leaks. Alors que le ramasse-miettes prend en charge les memory leaks “classiques”, il existe toujours, par exemple, le problème de la libération des ressources dites non managées (telles que les connexions de firebase database, les fichiers ouverts, les éléments graphiques, etc.). C’est là que l’interface IDisposable entre en jeu.

En outre, j’ai récemment rencontré une fuite possible de mémoire dans un paramètre d’ interopérabilité .NET-COM . Les composants COM utilisent des comptes de référence pour décider quand ils peuvent être libérés. .NET ajoute à cela un autre mécanisme de comptage de référence qui peut être influencé par la classe statique System.Runtime.InteropServices.Marshal .

Après tout, vous devez toujours faire attention à la gestion des ressources, même dans un programme .NET.

Vous pouvez absolument avoir des memory leaks dans le code .NET. Certains objects vont, dans certains cas, se rooter (bien qu’ils soient généralement IDisposable ). Ne pas appeler Dispose() sur un object dans ce cas provoquera absolument une fuite de mémoire de style C / C ++ réelle avec un object alloué auquel vous n’avez aucun moyen de référencer.

Dans certains cas, certaines classes de temporisation peuvent avoir ce comportement, par exemple.

Dans tous les cas où vous avez une opération asynchrone qui peut se replanifier, vous avez une fuite potentielle. Async op va généralement rooter l’object callback, empêchant une collection. Lors de l’exécution, l’object est enraciné par le thread en cours d’exécution, puis l’opération nouvellement planifiée réenracine l’object.

Voici un exemple de code utilisant System.Threading.Timer .

 public class Test { static public int Main(ssortingng[] args) { MakeFoo(); GC.Collect(); GC.Collect(); GC.Collect(); System.Console.ReadKey(); return 0; } private static void MakeFoo() { Leaker l = new Leaker(); } } internal class Leaker { private Timer t; public Leaker() { t = new Timer(callback); t.Change(1000, 0); } private void callback(object state) { System.Console.WriteLine("Still alive!"); t.Change(1000, 0); } } 

Tout comme GlaDOS , l’object Leaker sera indéfiniment “toujours en vie” – pourtant, il n’y a aucun moyen d’accéder à l’object (sauf en interne, et comment l’object peut-il savoir quand il n’est plus référencé?)

Si vous ne faites pas référence aux applications utilisant .NET, que ces réponses traitent très bien, mais font en réalité référence à l’environnement d’exécution lui-même, elles peuvent techniquement présenter des memory leaks, mais l’implémentation du ramasse-miettes est probablement proche. -gratuit. J’ai entendu parler d’un cas où un bogue avait été trouvé où quelque chose à l’exécution, ou peut-être juste dans les bibliothèques standard, avait une fuite de mémoire. Mais je ne me souviens pas de ce que c’était (quelque chose de très obscur), et je ne pense pas que je pourrais le retrouver.

Eh bien. NET a un ramasse-miettes pour nettoyer les choses quand bon lui semble. C’est ce qui le sépare des autres langages non gérés.

Mais .NET peut avoir des memory leaks. Les fuites GDI sont fréquentes parmi les applications Windows Forms, par exemple. L’une des applications que j’ai aidé à développer cette expérience régulièrement. Et lorsque les employés du bureau en utilisent plusieurs fois à longueur de journée, il n’est pas rare qu’ils atteignent la limite de 10 000 objects GDI inhérente à Windows.

L’une des principales sources de memory leaks C / C ++ qui n’existent pas dans .Net consiste à désallouer la mémoire partagée

Ce qui suit provient d’un cours dirigé par Brad Abrams sur la conception de bibliothèques de classes .NET

“Eh bien, le premier point est, bien sûr, qu’il n’ya pas de memory leaks, non? Non, il ya encore des memory leaks? Eh bien, il y a un autre type de fuite de mémoire. ne le faites pas, dans l’ancien monde, vous aviez l’habitude de faire de la mémoire avec malloc, puis d’oublier de faire une référence libre ou d’append une version, ou peu importe la paire. possède toute la mémoire, et le ramasse-miettes libère ce matériel quand il n’y a plus de références. Mais il peut toujours y avoir des fuites, non? Quelles sont les sortes de fuites? Eh bien, si vous gardez une référence à cet object en vie alors le ramasse-miettes ne peut pas libérer ça. Tant de fois, ce qui se passe est que vous pensez vous être débarrassé de tout ce graphe d’objects, mais il y a encore un gars qui s’y accroche avec une référence, et puis vous Le collecteur de mémoire ne peut pas le libérer tant que vous n’y renoncez pas.

Je pense que l’autre est un gros problème. Aucun problème de propriété de mémoire. Si vous lisez la documentation de l’API WIN32, vous verrez, d’accord, j’alloue d’abord cette structure et la transmet, puis vous la remplissez, puis je la libère. Ou est-ce que je vous dis la taille et que vous l’affectez et puis je la libère plus tard ou vous savez, tous ces débats se poursuivent pour savoir qui possède cette mémoire et où elle doit être libérée. Et plusieurs fois, les développeurs abandonnent cela et disent: «OK, peu importe. Eh bien, ce sera gratuit lorsque l’application sera fermée », et ce n’est pas un si bon plan.

Dans notre monde, le ramasse-miettes possède toute la mémoire gérée, il n’y a donc aucun problème de propriété de mémoire, que vous l’ayez créé et que vous le transmettiez à l’application, que l’application crée et que vous commencez à l’utiliser. Cela ne pose aucun problème, car il n’y a pas d’ambiguïté. Le ramasse-miettes possède tout. ”

Transcription complète

N’oubliez pas que la différence entre un cache et une fuite de mémoire est la règle. Si votre cache a une mauvaise stratégie (ou pire, aucune) pour supprimer des objects, il est impossible de la distinguer d’une fuite de mémoire.

Cette référence montre comment les fuites peuvent se produire dans .Net en utilisant des modèles d’événements faibles. http://msdn.microsoft.com/en-us/library/aa970850.aspx

.NET peut avoir des memory leaks mais cela aide beaucoup à les éviter. Tous les objects de type référence sont alloués à partir d’un segment géré qui suit les objects actuellement utilisés (les types de valeur sont généralement affectés à la stack). Chaque fois qu’un nouvel object de type référence est créé dans .NET, il est alloué à partir de ce segment géré. Le garbage collector est chargé d’exécuter et de libérer périodiquement tout object qui n’est plus utilisé (qui n’est plus référencé par aucune autre application).

Le livre de Jeffrey Richter, CLR via C #, contient un bon chapitre sur la gestion de la mémoire dans .NET.

Le meilleur exemple que j’ai trouvé provient de Java, mais le même principe s’applique à C #.

Nous lisions dans des fichiers texte composés de nombreuses longues lignes (chaque ligne contenait quelques Mo de mémoire). A partir de chaque fichier, nous avons recherché quelques sous-chaînes clés et avons conservé uniquement les sous-chaînes. Après avoir traité quelques centaines de fichiers texte, nous n’avons plus de mémoire.

Il s’est avéré que ssortingng.subssortingng (…) conservait une référence à la longue chaîne d’origine … même si nous ne conservions que 1000 caractères environ, ces sous-chaînes continueraient à utiliser plusieurs Mo de mémoire chacune. En effet, nous avons conservé le contenu de chaque fichier en mémoire.

Voici un exemple de référence qui a provoqué une fuite de mémoire. La méthode de sous-chaîne essayait de réutiliser des objects, mais a fini par gaspiller de la mémoire.

Edit: Vous ne savez pas si ce problème spécifique affecte .NET. L’idée était d’illustrer une conception / optimisation réelle effectuée dans un langage récupéré qui, dans la plupart des cas, était intelligent et utile, mais qui peut entraîner une utilisation indésirable de la mémoire.

Qu’en est-il si vous utilisez une DLL gérée mais que la DLL contient un code non sécurisé? Je sais que cela divise les cheveux, mais si vous n’avez pas le code source, alors, de votre sharepoint vue, vous n’utilisez que du code géré, mais vous pouvez toujours fuir.