web-dev-qa-db-fra.com

Fuite de mémoire en C #

Est-il possible dans un système géré de perdre de la mémoire lorsque vous vous assurez que tous les descripteurs, éléments implémentant IDispose, sont supprimés? 

Y a-t-il des cas où certaines variables sont omises?

54
Joan Venge

Les gestionnaires d'événements sont une source très courante de fuites de mémoire non évidentes. Si vous vous abonnez à un événement sur object1 à partir de object2, exécutez object2.Dispose () et prétendez qu'il n'existe pas (et supprimez toutes les références de votre code). ordures collectées.

MyType object2 = new MyType();

// ...
object1.SomeEvent += object2.myEventHandler;
// ...

// Should call this
// object1.SomeEvent -= object2.myEventHandler;

object2.Dispose();

Il s'agit d'un cas courant de fuite - oubliez de vous désabonner facilement des événements. Bien sûr, si objet1 est collecté, objet2 le sera également, mais pas avant.

63
Reed Copsey

Je ne pense pas que les fuites de mémoire de type C++ soient possibles. Le ramasse-miettes devrait en tenir compte. Il est possible de créer un objet statique qui agrège les références même si les objets ne sont plus jamais utilisés Quelque chose comme ça:

public static class SomethingFactory
{
    private static List<Something> listOfSomethings = new List<Something>();

    public static Something CreateSomething()
    {
        var something = new Something();
        listOfSomethings.Add(something);
        return something;
    }
}

C'est un exemple évidemment stupide, mais ce serait l'équivalent d'une fuite de mémoire d'exécution gérée.

40
Michael Meadows

Comme d'autres l'ont souligné, tant qu'il n'y a pas de bogue dans le gestionnaire de mémoire, les classes qui n'utilisent pas de ressources non gérées ne perdent pas de mémoire.

Ce que vous voyez dans .NET n'est pas une fuite de mémoire, mais des objets qui ne sont jamais éliminés. Un objet ne sera pas supprimé tant que le ramasse-miettes peut le trouver sur le graphique d'objet. Donc, si n'importe quel objet vivant a quelque part une référence à l'objet, il ne sera pas disposé.

L'inscription à un événement est un bon moyen d'y arriver. Si un objet s'inscrit pour un événement, quoi qu'il soit enregistré y soit référencé, et même si vous supprimez toutes les autres références à l'objet, jusqu'à ce qu'il se désenregistre (ou que l'objet avec lequel il s'est inscrit devienne inaccessible), il restera en vie.

Vous devez donc faire attention aux objets qui enregistrent des événements statiques à votre insu. Une caractéristique intéressante de la ToolStrip, par exemple, est que si vous modifiez votre thème d'affichage, il s'affichera automatiquement dans le nouveau thème. Il réalise cette fonctionnalité astucieuse en s'inscrivant pour l'événement statique SystemEvents.UserPreferenceChanged. Lorsque vous modifiez votre thème Windows, l'événement est déclenché et tous les objets ToolStrip qui l'écoutent sont avertis de la création d'un nouveau thème.

Bon, supposons que vous décidiez de jeter une ToolStrip sur votre formulaire:

private void DiscardMyToolstrip()
{
    Controls.Remove("MyToolStrip");
}

Vous avez maintenant une ToolStrip qui ne mourra jamais. Même si cela ne figure plus dans votre formulaire, chaque fois que l'utilisateur change de thème, Windows en informe consciencieusement la variable ToolStrip qui n'existe pas autrement. Chaque fois que le ramasse-miettes s'exécute, il se dit "je ne peux pas jeter cet objet, l'événement UserPreferenceChanged l'utilise".

Ce n'est pas une fuite de mémoire. Mais cela pourrait aussi bien être.

Ce genre de chose rend un profileur de mémoire inestimable. Lancez un profileur de mémoire et vous direz "c'est étrange, il semble y avoir dix mille ToolStrip objets sur le tas, même s'il n'y en a qu'un sur ma fiche. Comment cela est-il arrivé?"

Oh, et au cas où vous vous demanderiez pourquoi certaines personnes pensent que les personnes qui établissent une propriété sont pervers: pour obtenir un ToolStrip pour annuler l'inscription de l'événement UserPreferenceChanged, définissez sa propriété Visible sur false.

27
Robert Rossney

Les délégués peuvent entraîner des fuites de mémoire non intuitives.

Chaque fois que vous créez un délégué à partir d'une méthode d'instance, une référence à cette instance est stockée "dans" ce délégué.

En outre, si vous combinez plusieurs délégués dans un délégué de multidiffusion, vous disposez d'un gros blob de références à de nombreux objets qui ne peuvent pas être récupérés tant que ce délégué de multidiffusion est utilisé quelque part.

20

Si vous développez une application WinForms, une "fuite" subtile est la propriété Control.AllowDrop (utilisée pour activer le glisser-déposer). Si AllowDrop est défini sur "true", le CLR conservera tout de même votre contrôle via un System.Windows.Forms.DropTarget. Pour résoudre ce problème, assurez-vous que la propriété Control's AllowDrop est définie sur false lorsque vous n'en avez plus besoin et le CLR se chargera du reste.

17
Zach Johnson

La seule raison de la fuite de mémoire dans l'application .NET est que les objets sont toujours référencés bien que leur durée de vie soit terminée. Par conséquent, le ramasse-miettes ne peut pas les collecter. Et ils deviennent des objets de longue vie.

Je trouve qu'il est très facile de provoquer des fuites en souscrivant à des événements sans le désabonner lorsque la vie de l'objet se termine.

7
tranmq

Comme déjà mentionné, la conservation des références entraînera une augmentation de l'utilisation de la mémoire au fil du temps. Les événements sont un moyen facile d’entrer dans cette situation. Si vous avez un objet de longue vie avec un événement que vos autres objets écoutent, si les auditeurs ne sont jamais supprimés, l'événement de l'objet de longue vie gardera ces autres instances en vie bien après qu'elles ne seront plus nécessaires.

7
toad
6
Fabrice

La réflexion par émission est une autre source potentielle de fuites, avec par ex. désérialiseurs d'objets intégrés et clients SOAP/XML sophistiqués. Au moins dans les versions précédentes du framework, le code généré dans AppDomains dépendants n'a jamais été déchargé ...

5
Pontus Gagge

C'est un mythe que vous ne pouvez pas perdre de mémoire dans le code géré. Certes, c'est beaucoup plus difficile que dans le C++ non géré, mais il existe un million de façons de le faire. Objets statiques contenant des références, références inutiles, mise en cache, etc. Si vous faites les choses de la "bonne" façon, bon nombre de vos objets ne seront pas récupérés avant beaucoup plus tard que nécessaire, ce qui est aussi une fuite de mémoire, de manière pratique et non théorique.

Heureusement, il existe des outils qui peuvent vous aider. J'utilise beaucoup CLR Profiler de Microsoft - ce n'est pas l'outil le plus convivial jamais écrit, mais il est définitivement très utile et gratuit.

4
Tamas Czinege

Une fois que toutes les références à un objet ont disparu, le ramasse-miettes libère cet objet lors de son prochain passage. Je ne dirais pas qu'il est impossible de perdre de la mémoire, mais c'est assez difficile. Pour fuir, vous devez faire référence à un objet assis sans le savoir.

Par exemple, si vous instanciez des objets dans une liste puis oubliez de les supprimer de la liste lorsque vous avez terminé ET oubliez de les supprimer.

2
Kevin Laity

Il est possible que des fuites se produisent si les ressources non gérées ne sont pas nettoyées correctement. Les classes qui implémentent IDisposable peuvent fuir.

Cependant, les références d'objet standard ne nécessitent pas de gestion de mémoire explicite, contrairement aux langages de bas niveau.

1
Ben S

Pas vraiment une fuite de mémoire, mais il est assez facile de manquer de mémoire lorsque vous utilisez de gros objets (plus de 64 Ko si je me souviens bien). Ils sont stockés sur le disque et ne sont pas défragmentés. Donc, utiliser ces grands objets et les libérer libère la mémoire sur le LOH, mais cette mémoire libre n'est plus utilisée par le runtime .NET pour ce processus. Ainsi, vous pouvez facilement manquer d'espace sur le LOH en n'utilisant que quelques gros objets sur le LOH. Ce problème est connu de Microsoft, mais, si je me souviens bien, la solution est en cours de planification.

1
Oliver Friedrich

Un rappel de soiComment trouver une fuite de mémoire:

  • Supprimer et appels gc.collect. 
  • Attendez que la mémoire disparaisse.
  • Créez un fichier de vidage à partir du gestionnaire de tâches.
  • Ouvrez les fichiers de vidage à l’aide de DebugDiag .
  • Analysez le résultat en premier. Le résultat de là devrait nous aider, car isssue prend généralement la majeure partie de la mémoire. 
  • Corrigez le code jusqu'à ce qu'il ne reste plus aucune fuite de mémoire.
  • Utilisez une application tierce telle que .net profiler. (Nous pouvons utiliser trial, mais nous devons résoudre le problème dès que possible. Le premier vidage devrait nous aider principalement sur la manière dont la fuite a eu lieu)
  • Si le problème est dans la mémoire virtuelle, vous devez surveiller la mémoire non gérée. (Habituellement, une autre configuration doit être activée)
  • Exécutez une application tierce en fonction de son utilisation. 

Problème de fuite de mémoire commun:

  • Les événements/délégués ne sont jamais supprimés. (Lors de la suppression, assurez-vous que l'événement n'est pas enregistré) - voir ReepChopsey answer
  • La liste/le dictionnaire n’a jamais été effacé.
  • Un objet référencé à un autre objet enregistré en mémoire ne sera jamais supprimé. (Cloner pour une gestion plus facile)
0
Kosmas

Si cela est considéré comme une fuite de mémoire, cela pourrait également être réalisé avec ce type de code:

public class A
{
    B b;
    public A(B b) { this.b = b; }
    ~A()
    {
        b = new B();
    }
}

public class B
{
    A a;
    public B() { this.a = new A(this); }
    ~B()
    {
        a = new A(this);
    }
}

class Program
{
    static void Main(string[] args)
    {
        {
            B[] toBeLost = new B[100000000];
            foreach (var c in toBeLost)
            {
                toBeLost.ToString(); //to make JIT compiler run the instantiation above
            }
        }
        Console.ReadLine();
    }
}
0
meJustAndrew

Les petites fonctions aident à éviter les "fuites de mémoire". Parce que garbage collector libère les variables locales à la fin des fonctions. Si la fonction est volumineuse et prend beaucoup de mémoire, vous devez vous-même libérer des variables locales qui en prennent beaucoup et dont vous n’avez plus besoin. Les variables globales similaires (tableaux, listes) sont également mauvaises.

J'ai rencontré des fuites de mémoire en C # lors de la création d'images et non de leur élimination. Ce qui est un peu étrange. Les gens disent que vous devez appeler .Dispose () sur chaque objet qui l’a. Mais la documentation pour les fonctions graphiques C # ne le mentionne pas toujours, par exemple pour la fonction GetThumbnailImage (). Le compilateur C # devrait vous en avertir, je pense.

0
Tone Škoda

Vous pouvez avoir une fuite de mémoire lorsque vous utilisez .NET XmlSerializer, car il utilise un code non géré situé en dessous, qui ne sera pas supprimé.

Consultez la documentation et recherchez "fuite de mémoire" sur cette page:

https://docs.Microsoft.com/en-us/dotnet/api/system.xml.serialization.xmlserializer?view=netframework-4.7.2

0
FrankyHollywood

Lors de mon dernier emploi, nous utilisions une bibliothèque tierce .NET SQLite qui coulait comme un tamis.

Nous faisions beaucoup d'insertions rapides de données dans une situation étrange où la connexion à la base de données devait être ouverte et fermée à chaque fois. La bibliothèque tierce a fait une partie de l’ouverture de connexion que nous étions censés faire manuellement et ne l’a pas documentée. Il contenait également les références quelque part que nous n'avons jamais trouvé. Le résultat fut deux fois plus de connexions ouvertes que prévu et seulement une demi-fermeture. Et depuis que les références ont été retenues, nous avons eu une fuite de mémoire.

Ce n'est évidemment pas la même chose qu'une fuite de mémoire classique en C/C++, mais à toutes fins pratiques, c'était l'un pour nous.

0
Dinah

Bien qu'il soit possible que quelque chose dans le cadre ait une fuite, il est plus que probable que quelque chose ne soit pas éliminé correctement ou quelque chose empêche le GC de s'en débarrasser, IIS serait un élément primordial candidat pour cela. 

N'oubliez pas que tout dans .NET ne réside pas dans un code entièrement géré, une interopérabilité COM, des fichiers tels que des flux de fichiers, des requêtes de base de données, des images, etc. 

Un problème que nous avons eu il y a quelque temps (.net 2.0 sur IIS 6) était que nous allions créer une image puis la jeter, mais IIS ne libérerait pas la mémoire pendant un certain temps. .

0
Bob The Janitor

Les seules fuites (autres que des bogues lors de l'exécution pouvant être présents, mais pas très probablement dues au ramassage des ordures) vont concerner des ressources natives. Si vous P/Invoke dans une bibliothèque native qui ouvre des handles de fichiers, ou des connexions de socket, ou quoi que ce soit au nom de votre application gérée, et que vous ne les fermiez jamais explicitement (et ne les gérez pas dans un éliminateur ou un destructeur/finaliseur), vous pouvez avoir des fuites de mémoire ou de ressources, car le moteur d’exécution ne peut pas gérer toutes ces tâches automatiquement pour vous.

Si vous vous en tenez à des ressources purement gérées, cela devrait aller. Si vous rencontrez une fuite de mémoire sans avoir à appeler du code natif, c'est un bogue.

0
Michael Trausch

Dans une console ou une application Windows, créez un objet Panel (panel1), puis ajoutez 1 000 PictureBox dont la propriété Image est définie, puis appelez panel1.Controls.Clear. Tous les contrôles PictureBox sont encore dans la mémoire et aucun moyen GC ne peut les collecter:

var panel1 = new Panel();
var image = Image.FromFile("image/heavy.png");
for(var i = 0; i < 1000;++i){
  panel1.Controls.Add(new PictureBox(){Image = image});
}
panel1.Controls.Clear(); // => Memory Leak!

La bonne façon de le faire serait 

for (int i = panel1.Controls.Count-1; i >= 0; --i)
   panel1.Controls[i].Dispose();

Fuites de mémoire lors de l'appel de Controls.Clear ()

L'appel de la méthode Clear ne supprime pas les poignées de contrôle de la mémoire . Vous devez explicitement appeler la méthode Dispose pour éviter les fuites de mémoire.

0
Daniel B