Anatomie d'une «fuite de mémoire»

173

Dans la perspective .NET:

  • Qu'est-ce qu'une fuite de mémoire ?
  • Comment pouvez-vous déterminer si votre application fuit? Quels sont les effets?
  • Comment éviter une fuite de mémoire?
  • Si votre application a une fuite de mémoire, disparaît-elle lorsque le processus se termine ou est arrêté? Ou est-ce que les fuites de mémoire dans votre application affectent d'autres processus sur le système même après l'achèvement du processus?
  • Et qu'en est-il du code non géré accessible via COM Interop et / ou P / Invoke?
Huseyint
la source

Réponses:

110

La meilleure explication que j'ai vue se trouve au chapitre 7 du livre électronique gratuit Foundations of Programming .

Fondamentalement, dans .NET, une fuite de mémoire se produit lorsque les objets référencés sont enracinés et ne peuvent donc pas être récupérés. Cela se produit accidentellement lorsque vous conservez des références au-delà de la portée prévue.

Vous saurez que vous avez des fuites lorsque vous commencez à obtenir des OutOfMemoryExceptions ou que votre utilisation de la mémoire dépasse ce à quoi vous vous attendez ( PerfMon a de jolis compteurs de mémoire).

Comprendre le modèle de mémoire de .NET est votre meilleur moyen de l'éviter. Plus précisément, comprendre comment fonctionne le garbage collector et comment fonctionnent les références - encore une fois, je vous renvoie au chapitre 7 du livre électronique. Aussi, soyez conscient des pièges courants, probablement les événements les plus courants. Si l' objet A est inscrit à un événement sur l' objet B , alors l' objet A collera autour jusqu'à ce que l' objet B disparaît parce que B contient une référence à A . La solution consiste à désenregistrer vos événements lorsque vous avez terminé.

Bien sûr, un bon profil de mémoire vous permettra de voir vos graphiques d'objets et d'explorer l'imbrication / référencement de vos objets pour voir d'où viennent les références et quel objet racine est responsable ( profil de fourmis porte-rouge , JetBrains dotMemory, memprofiler sont vraiment bons choix, ou vous pouvez utiliser le texte uniquement WinDbg et SOS , mais je recommande fortement un produit commercial / visuel à moins que vous ne soyez un vrai gourou).

Je crois que le code non managé est sujet à ses fuites de mémoire typiques, sauf que les références partagées sont gérées par le garbage collector. Je peux me tromper sur ce dernier point.

Karl Seguin
la source
11
Oh tu aimes le livre, n'est-ce pas? J'ai vu l'auteur apparaître de temps en temps sur stackoverflow.
Johnno Nolan
Certains objets .NET peuvent également s'enraciner et devenir irrécupérables. Tout ce qui est IDisposable doit être éliminé pour cette raison.
kyoryu le
1
@kyoryu: Comment un objet s'enracine-t-il?
Andrei Rînea
2
@Andrei: Je pense qu'un Thread en cours d'exécution est peut-être le meilleur exemple d'un objet s'enracinant lui-même. Un objet qui met une référence à lui-même dans un emplacement statique non public (comme l'abonnement à un événement statique, ou l'implémentation d'un singleton par initialisation de champ statique) peut aussi bien s'être enraciné, car il n'y a pas de moyen évident de ... um ... le "déraciner" de son amarrage.
Jeffrey Hantin le
@Jeffry c'est une façon non conventionnelle de décrire ce qui se passe et j'aime ça!
Exitos le
35

À proprement parler, une fuite de mémoire consomme de la mémoire qui n'est «plus utilisée» par le programme.

«Plus utilisé» a plus d'un sens, cela pourrait signifier «plus aucune référence à lui», c'est-à-dire totalement irrécupérable, ou cela pourrait signifier, référencé, récupérable, inutilisé mais le programme garde quand même les références. Seul le dernier s'applique à .Net pour les objets parfaitement gérés . Cependant, toutes les classes ne sont pas parfaites et à un moment donné, une implémentation non gérée sous-jacente pourrait perdre des ressources de manière permanente pour ce processus.

Dans tous les cas, l'application consomme plus de mémoire que strictement nécessaire. Les effets secondaires, en fonction de la quantité divulguée, pourraient aller de zéro, à un ralentissement causé par une collecte excessive, à une série d'exceptions de mémoire et enfin à une erreur fatale suivie d'un arrêt forcé du processus.

Vous savez qu'une application a un problème de mémoire lorsque la surveillance montre que de plus en plus de mémoire est allouée à votre processus après chaque cycle de garbage collection . Dans ce cas, vous gardez trop de mémoire en mémoire ou une implémentation non gérée sous-jacente fuit.

Pour la plupart des fuites, les ressources sont récupérées lorsque le processus est terminé, mais certaines ressources ne sont pas toujours récupérées dans certains cas précis, les poignées de curseur GDI sont réputées pour cela. Bien entendu, si vous disposez d'un mécanisme de communication interprocessus, la mémoire allouée dans l'autre processus ne sera pas libérée tant que ce processus ne la libère pas ou ne s'arrête pas.

Coincoin
la source
32

Je pense que les questions «qu'est-ce qu'une fuite de mémoire» et «quels sont les effets» ont déjà été bien répondues, mais je voulais ajouter quelques éléments sur les autres questions ...

Comment comprendre si votre application fuit

Une manière intéressante est d'ouvrir perfmon et d'ajouter des traces pour # octets dans tous les tas et collections # Gen 2 , dans chaque cas en regardant juste votre processus. Si l'exercice d'une fonctionnalité particulière entraîne une augmentation du nombre total d'octets et que la mémoire reste allouée après la prochaine collecte de génération 2, vous pouvez dire que la fonctionnalité perd de la mémoire.

Comment empêcher

D'autres bonnes opinions ont été données. J'ajouterais simplement que peut-être la cause la plus souvent négligée des fuites de mémoire .NET est d'ajouter des gestionnaires d'événements aux objets sans les supprimer. Un gestionnaire d'événements attaché à un objet est une forme de référence à cet objet, donc empêchera la collecte même après que toutes les autres références aient disparu. N'oubliez jamais de détacher les gestionnaires d'événements (en utilisant la -=syntaxe en C #).

La fuite disparaît-elle lorsque le processus se termine et qu'en est-il de l'interopérabilité COM?

Lorsque votre processus se termine, toute la mémoire mappée dans son espace d'adressage est récupérée par le système d'exploitation, y compris tous les objets COM servis à partir des DLL. Comparativement, les objets COM peuvent être servis à partir de processus séparés. Dans ce cas, lorsque votre processus se termine, vous pouvez toujours être responsable de la mémoire allouée dans tous les processus de serveur COM que vous avez utilisés.

Martin
la source
19

Je définirais les fuites de mémoire comme un objet ne libérant pas toute la mémoire allouée après son achèvement. J'ai trouvé que cela peut arriver dans votre application si vous utilisez Windows API et COM (c'est-à-dire du code non géré qui contient un bogue ou n'est pas géré correctement), dans le cadre et dans des composants tiers. J'ai également constaté que le fait de ne pas se réveiller après avoir utilisé certains objets comme des stylos pouvait causer le problème.

J'ai personnellement souffert d'exceptions de mémoire insuffisante qui peuvent être causées mais ne sont pas exclusives à des fuites de mémoire dans les applications dot net. (Le MOO peut également provenir de l'épinglage, voir Épingler Artical ). Si vous n'obtenez pas d'erreurs MOO ou si vous avez besoin de confirmer si c'est une fuite de mémoire qui en est la cause, le seul moyen est de profiler votre application.

J'essaierais également d'assurer ce qui suit:

a) Tout ce qui implémente Idisposable est éliminé soit en utilisant un bloc finally ou l'instruction using, cela inclut des pinceaux, des stylos, etc. (certaines personnes soutiennent de tout remettre à rien en plus)

b) Tout ce qui a une méthode close est à nouveau fermé en utilisant finally ou l'instruction using (bien que j'ai trouvé que using ne se ferme pas toujours selon si vous avez déclaré l'objet en dehors de l'instruction using)

c) Si vous utilisez du code non géré / des API Windows, celles-ci sont traitées correctement après. (certains ont des méthodes de nettoyage pour libérer des ressources)

J'espère que cela t'aides.

John
la source
19

Si vous devez diagnostiquer une fuite de mémoire dans .NET, vérifiez ces liens:

http://msdn.microsoft.com/en-us/magazine/cc163833.aspx

http://msdn.microsoft.com/en-us/magazine/cc164138.aspx

Ces articles décrivent comment créer un vidage mémoire de votre processus et comment l'analyser afin que vous puissiez d'abord déterminer si votre fuite n'est pas gérée ou gérée, et si elle est gérée, comment déterminer d'où elle provient.

Microsoft dispose également d'un outil plus récent pour aider à générer des vidages sur incident, pour remplacer ADPlus, appelé DebugDiag.

http://www.microsoft.com/downloads/details.aspx?FamilyID=28bd5941-c458-46f1-b24d-f60151d875a3&displaylang=en

Eric Z Beard
la source
15

La meilleure explication du fonctionnement du ramasse-miettes se trouve dans Jeff Richters CLR via le livre C # , (Ch. 20). La lecture de ceci donne une bonne base pour comprendre comment les objets persistent.

L'une des causes les plus courantes d'enracinement accidentel d'objets est le raccordement d'événements hors d'une classe. Si vous connectez un événement externe

par exemple

SomeExternalClass.Changed += new EventHandler(HandleIt);

et oubliez de le décrocher lorsque vous supprimez, alors SomeExternalClass a une référence à votre classe.

Comme mentionné ci-dessus, le profileur de mémoire SciTech est excellent pour vous montrer les racines des objets que vous soupçonnez de fuir.

Mais il existe également un moyen très rapide de vérifier un type particulier est simplement d'utiliser WnDBG (vous pouvez même l'utiliser dans la fenêtre immédiate de VS.NET en étant attaché):

.loadby sos mscorwks
!dumpheap -stat -type <TypeName>

Maintenant, faites quelque chose qui, selon vous, éliminera les objets de ce type (par exemple, fermez une fenêtre). Il est pratique ici d'avoir un bouton de débogage quelque part qui s'exécutera System.GC.Collect()plusieurs fois.

Puis exécutez à !dumpheap -stat -type <TypeName>nouveau. Si le nombre n'a pas baissé ou n'a pas baissé autant que prévu, vous avez une base pour une enquête plus approfondie. (J'ai eu cette astuce d'un séminaire donné par Ingo Rammer ).

Gus Paul
la source
14

Je suppose que dans un environnement géré, une fuite serait que vous gardiez une référence inutile à une grande partie de la mémoire.

Bernard
la source
11

Pourquoi les gens pensent-ils qu'une fuite de mémoire dans .NET n'est pas la même chose qu'une autre fuite?

Une fuite de mémoire se produit lorsque vous vous attachez à une ressource et ne la laissez pas partir. Vous pouvez le faire à la fois en codage géré et non géré.

En ce qui concerne .NET et d'autres outils de programmation, il y a eu des idées sur le ramassage des ordures et d'autres moyens de minimiser les situations qui feront fuir votre application. Mais la meilleure méthode pour éviter les fuites de mémoire consiste à comprendre votre modèle de mémoire sous-jacent et son fonctionnement sur la plate-forme que vous utilisez.

Croire que GC et d'autres magies nettoieront votre désordre est le chemin le plus court vers les fuites de mémoire, et sera difficile à trouver plus tard.

Lors du codage non géré, vous vous assurez normalement de nettoyer, vous savez que les ressources que vous prenez en main seront de votre responsabilité de nettoyer, pas celles du concierge.

Dans .NET en revanche, beaucoup de gens pensent que le GC va tout nettoyer. Eh bien, il en fait pour vous, mais vous devez vous assurer qu'il en est ainsi. .NET englobe beaucoup de choses, vous ne savez donc pas toujours si vous avez affaire à une ressource gérée ou non gérée, et vous devez vous assurer de ce à quoi vous avez affaire. La gestion des polices, des ressources GDI, de l'annuaire actif, des bases de données, etc. est généralement des choses que vous devez rechercher.

En termes gérés, je vais mettre mon cou en jeu pour dire qu'il disparaît une fois que le processus est tué / supprimé.

Je vois que beaucoup de gens ont cela, et j'espère vraiment que cela prendra fin. Vous ne pouvez pas demander à l'utilisateur de mettre fin à votre application pour nettoyer votre désordre! Jetez un œil à un navigateur, qui peut être IE, FF, etc., puis ouvrez, disons, Google Reader, laissez-le rester pendant quelques jours et regardez ce qui se passe.

Si vous ouvrez ensuite un autre onglet dans le navigateur, surfez sur un site, puis fermez l'onglet qui hébergeait l'autre page qui a fait la fuite du navigateur, pensez-vous que le navigateur libérera la mémoire? Ce n'est pas le cas avec IE. Sur mon ordinateur, IE consommera facilement 1 Gio de mémoire en peu de temps (environ 3-4 jours) si j'utilise Google Reader. Certaines pages d'actualités sont encore pires.

neslekkiM
la source
10

Je suppose que dans un environnement géré, une fuite serait que vous gardiez une référence inutile à une grande partie de la mémoire.

Absolument. En outre, ne pas utiliser la méthode .Dispose () sur des objets jetables le cas échéant peut provoquer des fuites de mémoire. Le moyen le plus simple de le faire est d'utiliser un bloc using car il exécute automatiquement .Dispose () à la fin:

StreamReader sr;
using(sr = new StreamReader("somefile.txt"))
{
    //do some stuff
}

Et si vous créez une classe qui utilise des objets non gérés, si vous n'implémentez pas correctement IDisposable, vous pourriez provoquer des fuites de mémoire pour les utilisateurs de votre classe.

Seibar
la source
9

Toutes les fuites de mémoire sont résolues par l'arrêt du programme.

Fuite de mémoire suffisante et le système d'exploitation peut décider de résoudre le problème en votre nom.

Josh
la source
8

Je serai d'accord avec Bernard pour dire dans .net ce que serait une fuite de mémoire.

Vous pouvez profiler votre application pour voir son utilisation de la mémoire et déterminer que si elle gère beaucoup de mémoire alors qu'elle ne devrait pas l'être, vous pourriez dire qu'elle a une fuite.

En termes gérés, je vais mettre mon cou en jeu pour dire qu'il disparaît une fois que le processus est tué / supprimé.

Le code non managé est sa propre bête et s'il y a une fuite en son sein, il suivra un mem standard. définition des fuites.

Tapoter
la source
7

Gardez également à l'esprit que .NET a deux tas, l'un étant le tas d'objets volumineux. Je crois que des objets d'environ 85k ou plus sont placés sur ce tas. Ce tas a des règles de durée de vie différentes de celles du tas normal.

Si vous créez de grandes structures de mémoire (dictionnaire ou liste), il serait prudent d'aller chercher quelles sont les règles exactes.

En ce qui concerne la récupération de la mémoire à la fin du processus, sauf si vous exécutez Win98 ou ses équivalents, tout est remis au système d'exploitation à la fin. Les seules exceptions sont les éléments ouverts entre processus et un autre processus a toujours la ressource ouverte.

Les objets COM peuvent être difficiles à gérer. Si vous utilisez toujours le IDisposemodèle, vous serez en sécurité. Mais j'ai rencontré quelques assemblys d'interopérabilité qui implémentent IDispose. La clé ici est d'appeler Marshal.ReleaseCOMObjectlorsque vous avez terminé. Les objets COM utilisent toujours le comptage de référence COM standard.

Joël Lucsy
la source
6

J'ai trouvé .Net Memory Profiler une très bonne aide pour trouver des fuites de mémoire dans .Net. Ce n'est pas gratuit comme le Microsoft CLR Profiler, mais c'est plus rapide et plus pertinent à mon avis. UNE

Lars Truijens
la source