8
votes

Question sur le collecteur des ordures dans .NET (fuite de mémoire)

Je suppose que c'est très basique mais depuis que j'apprends .net par moi-même, je dois poser cette question.

Je suis habitué à coder dans C, où vous devez gratuit () tout. En C ++ /. NET, j'ai lu sur le collecteur des ordures. D'après ce que je comprends, quand une instance n'est plus utilisée (dans la portée de l'objet), elle est libérée par le collecteur des ordures.

Ainsi, autant à l'esprit, j'ai construit une petite application de test. Mais, il semble que je n'ai pas eu quelque chose parce que lorsque vous faites les mêmes choses à quelques reprises (comme, ouvrant une forme, la fermeture, la rouvrir, etc.), des fuites de mémoire. Et grand temps.

J'ai essayé de regarder ça sur Google, mais je n'ai rien trouvé de bon pour un débutant.

  1. est le collecteur des ordures libérant vraiment tous les objets lorsqu'il n'est plus utilisé ni des exceptions que je dois gérer? Qu'est-ce que je manque?
  2. Y a-t-il des outils gratuits pour rechercher des fuites de mémoire?

9 commentaires

Comment savez-vous que la mémoire fuit?


Je suivais la mémoire dans le chef de la tâche. Voir les autres deux commentaires que j'ai faits ci-dessous. Peut-être que ce n'est pas une "fuite" mais c'est ennuyeux néanmoins.


@Procule - Vous allez vous inquiéter si le CLR GC fonctionne. Cela fait. Beaucoup de codeurs C / C ++ l'utilisent et sont vraiment heureux avec cela. Cependant, vous devez en apprendre davantage sur le motif utilisant / Idisposable et pour vérifier les documents et voir si un objet implémente Idisposable. Mais le grand avantage de .NET n'est pas la microgestation ce genre de chose.


Par "gestionnaire de tâches" parlez-vous du Windows "Task Manager"? Le gestionnaire de tâches Windows n'est pas un bon endroit pour voir l'utilisation de la mémoire car la mémoire est attribuée dans d'énormes blocs (dont la plupart ne sont pas retournés au système d'exploitation). Bien que la mémoire dans les blocs ne soit pas utilisée, l'application conserve les énormes blocs pour les besoins de la mémoire futurs.


(J'ai passé beaucoup de temps sur ce genre de chose au début aussi)


@ovoverSlacked: Pourriez-vous me donner des msdn ou des tutoriels à Idisposable s'il vous plaît? Est déployé () comme gratuit ()?


Martin, c'est ce que je pensais. Je suppose qu'il y a des "frais généraux" dans cette utilisation de la mémoire. Ou quoi que ce soit appelé. C'est toujours des fenêtres! Connaissez-vous une meilleure façon de regarder la mémoire?


@Procule - J'ai ajouté une nouvelle réponse avec des liens; Bonne chance!


Mémoire virtuelle! = Bytes privés! = Set de travail.


7 Réponses :


3
votes

Le GC ne sera pas nécessairement des choses libres pour le moment où l'objet n'est plus référencé. Le GC le collectera à un moment donné à l'avenir - vous ne savez pas exactement quand, mais si la mémoire est nécessaire, le GC effectuera une collection si nécessaire.


1 commentaires

C'est quelque chose que j'ai découvert. Tout en regardant le responsable de la tâche, après une fois, une mémoire a été libérée. Mais, lors du démarrage du programme, il faut 20 mesgs, allez à 80 mesgs, puis tombez à 60 mesgs. C'est toujours 40 mesgs et je suis censé être dans le même "État".



8
votes

Qu'est-ce qui vous amène à conclure qu'il y a des fuites de mémoire? Sous la collecte des ordures, rien ne garantit que la mémoire est libérée immédiatement et, en général, le GC ne frappe pas jusqu'à ce que votre mémoire de processus atteigne un seuil ou que le tas disponible a été épuisé. (La heuristique exacte est compliquée et non importante.) Donc, le fait que la mémoire de votre processus monte et ne descend pas ne signifie pas nécessairement qu'il y a un bug. Il se peut que le GC ne s'est pas encore contourné de nettoyer votre processus.

En outre, êtes-vous sûr qu'il n'y a pas de références à vos objets? Il est possible que vous ayez des références que vous n'êtes pas au courant. La plupart des fuites de mémoire dans les applications .NET sont parce que les gens ne se rendent pas compte que leur mémoire est toujours référencée quelque part.


5 commentaires

Je comprends ton point. (Voir le premier commentaire à "Michael Burr"). Mais aller de 20 mesgs à 80 mesgs ... Cela semble un peu élevé pour moi. Dois-je disposer () l'objet manuellement?


Que voulez-vous Dispose () ? Généralement, la plupart des objets qui implémentent l'interface Idisposable doivent être disposés. Je ne suis pas sûr de C ++ / CLR, mais en C # qui est souvent fait avec le en utilisant construction. Cependant, dans le cas des objets de forme, la vie est un peu différente différente. Et comprenez que le CLR-lui-même a une empreinte modérément lourde, et aussi que le rapport de mémoire que vous voyez dans Task Manager est probablement éventuellement inexact. (Utilisez des fourmis ou un autre bon profileur pour être certain que vous avez vraiment une fuite.)


Merci, vous semblez répondre à ma deuxième question. Les "fourmis" sont donc un programme pour m'aider à gérer / regarder la mémoire? (BTW, la moitié de mon code est en C ++ / CLI et l'autre moitié en C # (j'ai changé à mi-chemin, il semble que c # C'est beaucoup plus facile!))


Personnellement, je conviendrais que c # est plus facile. OTOH, je ne suis pas une grande partie d'un développeur C ++. (En fait, j'irais si loin de dire que c'est carrément dangereux de me mettre sur un projet C ++.) Les fourmis sont des logiciels commerciaux, mais il y a un essai de 14 jours disponible. red-gate.com/products/ants_performance_profiler/index.htm


Ouais j'ai vu ça. Y a-t-il une alternative gratuite car elle est juste pour apprendre?



12
votes

Ouais, le collecteur des ordures libère vos objets quand ils ne sont plus utilisés.

Qu'est-ce que nous appelons habituellement une fuite de mémoire dans .NET, c'est plus comme:

  • Vous utilisez des ressources externes (qui ne sont pas des ordures recueillies) et d'oublier de les libérer. Ceci est résolu généralement en mettant en œuvre l'interface Idisposable.
  • Vous pensez qu'il n'y a pas de références à un objet donné, mais il y a en effet, quelque part et que vous n'utilisez plus que le collectionneur des ordures ne le sait pas.

    De plus, la mémoire n'est récupérée que si nécessaire, ce qui signifie que le collecteur de déchets s'active à des moments donnés et effectue une collection qui les a déterminé quelle mémoire peut être libérée et la libérer. Jusqu'à ce qu'il soit, la mémoire n'est pas revendiquée, il peut sembler que la mémoire est perdue.

    Ici, je pense que je vais fournir une réponse plus complexe juste pour clarifier.

    Premièrement, le collecteur des ordures s'exécute dans son propre fil. Vous devez comprendre cela, afin que la mémoire récupère la mémoire, le collectionneur des ordures doit empêcher tous les autres threads afin qu'il puisse suivre la chaîne de référence une détermination de ce qui dépend de quoi. C'est la raison que la mémoire n'est pas libérée immédiatement, un collecteur ordures implique certains coûts de performance.

    En interne, le collecteur des ordures gère la mémoire dans les générations. De manière très simplifiée, il y a plusieurs générations pour des objets de longue taille, de courte durée et de grande taille. Le collecteur des ordures déplace l'objet d'une génération à une autre chaque fois que sa fonction est une collection qui se produit plus souvent pour de courte génération vécue que pendant de longues personnes. Il réaffecte également des objets pour vous procurer l'espace contigu le plus possible, à nouveau, effectuer une collection est un processus coûteux.

    Si vous voulez vraiment voir les effets de vous libérant le formulaire (lors de la sortie de la portée et n'ayant pas plus de référence à cela), vous pouvez appeler gc.collect () . Faites-le juste pour les tests, il est fortement peu judicieux de téléphoner à la collecte sauf un très peu de cas où vous savez exactement ce que vous faites et les implications que cela aura.

    Un peu plus expliqué sur la méthode d'élimination de l'interface Idisose.

    Dispose n'est pas un destructeur de la manière habituelle C ++, ce n'est pas un destructeur du tout. Dispose est un moyen de de manière déterministe de se débarrasser des objets non gérés . Un exemple: Supposons que vous appeliez une bibliothèque COM Externe qui alloue 1 Go de mémoire en raison de ce qu'elle fait. Si vous ne disposez pas de la mémoire si vous disposez de la mémoire, gaspiller de l'espace jusqu'à ce que le GC introduit une collection et récupère la mémoire non gérée en appelant le destructeur d'objet réel. Donc, si vous voulez libérer la mémoire immédiate, vous devez appeler la méthode de disposer, mais que vous n'êtes pas "forcé" de le faire.

    Si vous n'utilisez pas une interface iDisposable, vous devez libérer vos ressources non gérées dans la méthode de finalisation. Finaliser est automatiquement appelé à partir du destructeur d'objet lorsque le GC tente de récupérer l'objet. Donc, si vous avez une méthode de finalisation appropriée, la mémoire non gérée sera libérée de toute façon. Appeler le dispositif ne le rendra que déterministe.


8 commentaires

(premier bouton) Voulez-vous dire un objet non géré? (deuxième bouton) Vous semblez dire les mêmes choses des autres. Ainsi, peut-être que l'objet est toujours référencé mais par un objet que je ne sais pas. Y a-t-il un moyen de savoir? Parce que le débogueur (à VS2008) ne vous indique que sur les habitants. (Je suis habitué à coder en C / Linux)


@Jorges: +1, Excellente explication de la GC et des générations. (Ce qui est très utile pour que quelqu'un commence à entrer .net et, malheureusement, mon expérience a montré l'un des articles les moins enseignés.)


Ouais, je veux dire des objets non gérés en général mais pas toujours les plus évidents. Par exemple, un pointeur fixe dangereux pourrait rendre un objet épinglé et arrêter l'objet de référence à déménager, ce qui pourrait entraîner une fragmentation de la mémoire et pourrait à leur tour permettre d'avoir beaucoup d'espace "gaspillé"


D'accord. Je comprends des pointeurs à coup sûr. Est-ce le rôle de ~ fonction () de faire cela? Dois-je toujours appeler le destructeur et libre () tous les objets?


Procule, je veux dire une erreur de votre part où vous avez, par exemple, un objet avec une propriété qui fait référence à un autre objet, mais que vous n'en êtes pas au courant. Le premier objet ne tombe jamais hors de portée et le deuxième objet n'est jamais libéré (imaginez qu'avec une très longue chaîne de référence). Comme dit dans d'autres commentaires, plusieurs profilers découvrent ces situations en soulignant quels objets prennent le plus de mémoire afin que vous puissiez décider si c'est vrai ou faux


Procule, j'ai édité la réponse pour expliquer à quel point le travail et la différence entre les désignables et les destructeurs sont idissibles.


Merci Jorge. Je comprends. Donc, si je n'ai pas d'objets non gérés, j'attends juste que le GC gère la mémoire? Pour moi, c'est juste ... Akward. Je suis habitué à libérer les structures quand ils ne sont plus utilisés. Donc, si je vous comprends bien, je fais bien, je fais bien et même si je vois la mémoire d'escalade dans le gestionnaire de tâches Windows, cela ne signifie rien sur la mémoire réelle utilisée. Est-ce correct ?


Oh et BTW, vous devriez +1 moi depuis que j'ai besoin de 15 points de rep. hehe



0
votes

Il existe des outils gratuits disponibles pour examiner le tas géré dans .NET, à l'aide du SOSEX Extensions à windbg et SOS , il est possible d'exécuter un programme, en la pause, puis regardez les objets existants sur la pile et (Plus important encore) quels autres objets les contentent de les références (racines) qui empêcheront le GC de les collecter.


0 commentaires

4
votes

J'ajoute cela comme une réponse plutôt qu'un commentaire sur la question, mais cela suit une question posée par le PO dans un commentaire: Utilisation de Déclaration sur MSDN. Interface Idisposable sur MSDN.

Voici le noeud du problème: ce que vous êtes habitué à ce que les objets destructeurs sont partis. Tout ce que vous avez dit de savoir comment CODURE correctement crier de votre subconscient, disant que cela ne peut pas être vrai, et s'il est vrai, c'est faux et terrible. C'est très différent; Il est difficile de se rappeler à quel point je l'ai vraiment méprisée au début (j'étais un développeur fier C ++).

Je vous promets personnellement: ça va être ok!

Voici une autre bonne chose à lire: Destructeurs et finaliseurs de Visual C ++ .


1 commentaires

lol j'aime comment tu le dis. Je voudrais +1 vous mais j'ai besoin de 15 points pour le faire. Merci !



7
votes

Le gestionnaire de tâches est un moyen terrible d'examiner votre utilisation de la mémoire. Si vous souhaitez étudier la manière dont le collecteur des ordures fonctionne, installez le profileur CLR et utilisez-le pour analyser votre application. Il vous dira exactement ce que fait le collecteur des ordures.

voir Comment: utiliser le profileur CLR .


2 commentaires

Merci pour le conseil, je regarde dans le profileur CLR. +1


J'irais avec Perfmon en premier, avant de plonger plus profondément avec Windbg ou un profileur CLR, car il s'agit beaucoup plus de poids plus léger et a un tas de comptoirs tels que la taille du tas de lob, la taille du tas, les comptes de collection, etc. ... aussi CLR Profiler utilise l'ICORPROFILER qui Ne prend pas en charge l'attachement, vous devez donc prendre votre décision de profiler



1
votes

Si vous voulez juste savoir si vous avez une fuite de mémoire ou non, consultez Perfmon qui expédie avec votre copie de Windows:

en particulier le compteur de mémoire .NET CLR octets dans tous les tas , si ce nombre augmente régulièrement, vous avez une fuite.

Vous pouvez même creuser plus profondément en comparant la taille du tas de Gen 2 à la taille de la grande quantité d'objet. Si le premier se développe régulièrement, vous avez un grand blob de données qui fuient.

Une fois que vous avez confirmé une fuite, vous pouvez joindre avec WINDBG et utiliser le Extensions SOS pour déterminer ce qui fuit.

Si vous pouvez vous permettre de passer quelques dollars, consultez le profileur de mémoire .NET .


0 commentaires