Question

Quelles sont toutes les manières possibles d’obtenir des fuites de mémoire dans .NET ?

J'en connais deux :

  1. Pas de désinscription correcte Gestionnaires d'événements/délégués.
  2. Ne pas supprimer les contrôles enfants dynamiques dans Windows Forms :

Exemple:

// Causes Leaks  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  

// Correct Code  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  
label.Dispose();

Mise à jour:L’idée est de lister les pièges courants qui ne sont pas trop évidents (comme celui ci-dessus).On pense généralement que les fuites de mémoire ne constituent pas un gros problème à cause du garbage collector.Pas comme c’était le cas en C++.


Excellente discussion les gars, mais laissez-moi clarifier...par définition, s'il n'y a plus de référence à un objet dans .NET, il sera récupéré à un moment donné.Ce n’est donc pas une façon de provoquer des fuites de mémoire.

Dans l'environnement géré, je considérerais qu'il s'agit d'une fuite de mémoire si vous aviez une référence involontaire à un objet dont vous n'êtes pas au courant (d'où les deux exemples de ma question).

Alors, quelles sont les différentes manières possibles pour qu’une telle fuite de mémoire puisse se produire ?

Était-ce utile?

La solution

Bloquez le thread du finaliseur.Aucun autre objet ne sera récupéré jusqu'à ce que le thread du finaliseur soit débloqué.Ainsi, la quantité de mémoire utilisée va croître de plus en plus.

Lectures complémentaires : http://dotnetdebug.net/2005/06/22/blocked-finalizer-thread/

Autres conseils

Cela ne provoque pas vraiment de fuites, cela fait simplement plus de travail pour le GC :

// slows GC
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  

// better  
Label label = new Label();  
this.Controls.Add(label);  
this.Controls.Remove(label);  
label.Dispose();

// best
using( Label label = new Label() )
{ 
    this.Controls.Add(label);  
    this.Controls.Remove(label);  
}

Laisser traîner des composants jetables comme celui-ci n'est jamais vraiment un problème dans un environnement géré comme .Net - c'est une grande partie de ce que signifie géré.

Vous ralentirez certainement votre application.Mais vous ne laisserez aucun gâchis pour autre chose.

Réglage du GridControl.DataSource propriété directement sans utiliser d’instance de la classe BindingSource (http://msdn.microsoft.com/en-us/library/system.windows.forms.bindingsource.aspx).

Cela a provoqué des fuites dans mon application qu'il m'a fallu un certain temps pour localiser avec un profileur. J'ai finalement trouvé ce rapport de bogue auquel Microsoft a répondu : http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=92260

C'est drôle que dans la documentation de la classe BindingSource, Microsoft essaie de la faire passer pour une classe légitime et bien pensée, mais je pense qu'ils viennent de la créer pour résoudre une fuite fondamentale concernant les gestionnaires de devises et la liaison des données aux contrôles de grille.

Méfiez-vous de celui-ci, je parie qu'il y a absolument plein d'applications qui fuient à cause de cela !

Il n'est pas possible de fournir une liste complète...c'est un peu comme demander « Comment peut-on se mouiller ? »

Cela dit, assurez-vous d'appeler Dispose() sur tout ce qui implémente IDisposable et assurez-vous d'implémenter IDisposable sur tous les types qui consomment des ressources non gérées de toute nature.

De temps en temps, exécutez quelque chose comme FxCop sur votre base de code pour vous aider à appliquer cette règle - vous seriez surpris de la profondeur avec laquelle certains objets jetables sont enfouis dans un cadre d'application.

Exceptions dans les méthodes Finalize (ou Dispose depuis un Finaliser) qui empêchent la suppression correcte des ressources non gérées.Un problème courant est dû au programmeur en supposant quel ordre les objets seront supprimés et essayer de libérer les objets homologues qui ont déjà été supprimés, ce qui entraînera une exception et le reste de la méthode Finalise/Dispose from Finalize ne sera pas appelé.

J'ai 4 éléments supplémentaires à ajouter à cette discussion :

  1. La fin des threads (Thread.Abort()) qui ont créé des contrôles d'interface utilisateur sans se préparer correctement à un tel événement peut conduire à une utilisation de la mémoire dans l'expectative.

  2. Accéder à des ressources non gérées via Pinvoke et ne pas les nettoyer peut entraîner des fuites de mémoire.

  3. Modification d'objets chaîne de grande taille.Pas nécessairement une fuite de mémoire, une fois hors de portée, GC s'en chargera, cependant, en termes de performances, votre système peut en prendre un coup si de grandes chaînes sont souvent modifiées, car vous ne pouvez pas vraiment compter sur GC pour garantir que l'empreinte de votre programme est minimal.

  4. Création fréquente d'objets GDI pour effectuer un dessin personnalisé.Si vous effectuez souvent des travaux GDI, réutilisez un seul objet GDI.

Appeler IDisposable à chaque fois est le point de départ le plus simple et certainement un moyen efficace de récupérer tous les fruits de fuite de mémoire faciles dans la base de code.Cependant, cela ne suffit pas toujours.Par exemple, il est également important de comprendre comment et quand le code managé est généré au moment de l'exécution, et qu'une fois les assemblys chargés dans le domaine d'application, ils ne sont jamais déchargés, ce qui peut augmenter l'empreinte de l'application.

Pour éviter les fuites de mémoire .NET :

1) Utilisez la construction « using » (ou la construction « try-finally ») chaque fois qu'un objet avec l'interface « IDisposable » est créé.

2) Créez des classes « IDisposable » si elles créent un thread ou si elles ajoutent un objet à une collection statique ou de longue durée.N'oubliez pas qu'un « événement » C# est une collection.

Voici un court article sur Conseils pour éviter les fuites de mémoire.

Parlez-vous d’une utilisation inattendue de la mémoire ou de fuites réelles ?Les deux cas que vous avez répertoriés ne sont pas exactement des fuites ;ce sont des cas où des objets restent plus longtemps que prévu.

En d’autres termes, ce sont des références que la personne qui les appelle fuites de mémoire ne connaissait pas ou avait oublié.

Modifier:Ou bien il s'agit de véritables bugs dans le garbage collector ou de code non géré.

Modifier 2 :Une autre façon d’y penser est de toujours vous assurer que les références externes à vos objets sont publiées de manière appropriée.Externe signifie un code hors de votre contrôle.Dans tous les cas où cela se produit, vous pouvez « fuir » de la mémoire.

  1. Conserver des références à des objets dont vous n’avez plus besoin.

Concernant les autres commentaires - une façon de garantir que Dispose soit appelé est d'utiliser using...lorsque la structure du code le permet.

Une chose qui était vraiment inattendue pour moi est la suivante :

Region oldClip = graphics.Clip;
using (Region newClip = new Region(...))
{
    graphics.Clip = newClip;
    // draw something
    graphics.Clip = oldClip;
}

Où est la fuite de mémoire ?C'est vrai, tu aurais dû disposer oldClip, aussi!Parce que Graphics.Clip est l'une des rares propriétés qui renvoie un nouvel objet jetable à chaque fois que le getter est invoqué.

Tess Fernandez a d'excellents articles de blog sur la recherche et le débogage des fuites de mémoire.Laboratoire 6 Laboratoire 7

De nombreux facteurs pouvant provoquer des fuites de mémoire dans les langages non gérés peuvent toujours provoquer des fuites de mémoire dans les langages gérés.Par exemple, mauvaises politiques de mise en cache peut entraîner des fuites de mémoire.

Mais comme Greg et Danny l’ont dit, il n’existe pas de liste exhaustive.Tout ce qui peut entraîner la conservation de la mémoire après sa durée de vie utile peut provoquer une fuite.

Les threads bloqués ne libéreront jamais de racines.On pourrait évidemment affirmer que l’impasse pose un problème plus grave.

Un thread de finaliseur bloqué empêchera tous les finaliseurs restants de s'exécuter et empêchera ainsi la récupération de tous les objets finalisables (car ils sont toujours enracinés par la liste accessible).

Sur une machine multi-CPU, vous pouvez créer des objets finalisables plus rapidement que le thread du finaliseur ne peut exécuter les finaliseurs.Tant que cela dure, vous perdrez de la mémoire.Il est peu probable que cela se produise dans la nature, mais il est facile à reproduire.

Le tas d'objets volumineux n'est pas compacté, vous pourriez donc perdre de la mémoire par fragmentation.

Il existe un certain nombre d'objets qui doivent être libérés manuellement.Par exemple.objets distants sans bail ni assemblys (doit décharger AppDomain).

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top