2010-11-20 8 views
13

J'ai une application qui présente des fuites de mémoire en raison d'événements qui ne sont pas détachés avant qu'une référence d'objet soit définie sur null. L'application est assez grande et il est difficile de trouver les fuites de mémoire en regardant le code. Je veux utiliser sos.dll pour trouver les noms des méthodes qui sont la source de fuites mais je suis bloqué. J'ai mis en place un projet de test pour démontrer le problème.Fuites de mémoire basées sur les événements C#

Ici, j'ai 2 classes, l'une avec un événement, et sur les écoutes à cet événement comme ci-dessous

namespace MemoryLeak 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      TestMemoryLeak testMemoryLeak = new TestMemoryLeak(); 

      while (!Console.ReadKey().Key.Equals('q')) 
      { 
      } 
     } 
    } 

    class TestMemoryLeak 
    { 
     public event EventHandler AnEvent; 

     internal TestMemoryLeak() 
     { 
      AnEventListener leak = new AnEventListener(); 
      this.AnEvent += (s, e) => leak.OnLeak(); 
      AnEvent(this, EventArgs.Empty); 
     } 

    } 

    class AnEventListener 
    { 
     public void OnLeak() 
     { 
      Console.WriteLine("Leak Event"); 
     } 
    } 
} 

je casse dans le code, et le type de fenêtre intermédiaire

.load sos.dll 

puis-je utiliser! dumpheap pour obtenir les objets sur le tas du type AnEventListener

!dumpheap -type MemoryLeak.AnEventListener 

et je reçois la fo llowing

PDB symbol for mscorwks.dll not loaded 
Address  MT  Size 
01e19254 0040348c  12  
total 1 objects 
Statistics: 
     MT Count TotalSize Class Name 
0040348c  1   12 MemoryLeak.AnEventListener 
Total 1 objects 

J'utilise! gcroot de comprendre pourquoi l'objet n'est pas des déchets collectés

!gcroot 01e19254 

et obtenir les éléments suivants

!gcroot 01e19254 
Note: Roots found on stacks may be false positives. Run "!help gcroot" for 
more info. 
Error during command: Warning. Extension is using a callback which Visual Studio 
does not implement. 

Scan Thread 5208 OSTHread 1458 
ESP:2ef3cc:Root:01e19230(MemoryLeak.TestMemoryLeak)-> 
01e19260(System.EventHandler)-> 
01e19248(MemoryLeak.TestMemoryLeak+<>c__DisplayClass1)-> 
01e19254(MemoryLeak.AnEventListener) 
Scan Thread 7376 OSTHread 1cd0 

je peux voir maintenant le gestionnaire d'événements qui est la source de la fuite. Je l'utilise! Faire pour regarder les champs du gestionnaire d'événements et obtenir

!do 01e19260 
Name: System.EventHandler 
MethodTable: 65129dc0 
EEClass: 64ec39d0 
Size: 32(0x20) bytes 
    (C:\Windows\assembly\GAC_32\mscorlib\2.0.0.0__b77a5c561934e089\mscorlib.dll) 
Fields: 
     MT Field Offset     Type VT  Attr Value Name 
65130770 40000ff  4  System.Object 0 instance 01e19248 _target 
6512ffc8 4000100  8 ...ection.MethodBase 0 instance 00000000 _methodBase 
6513341c 4000101  c  System.IntPtr 1 instance 0040C060 _methodPtr 
6513341c 4000102  10  System.IntPtr 1 instance 00000000 _methodPtrAux 
65130770 400010c  14  System.Object 0 instance 00000000 _invocationList 
6513341c 400010d  18  System.IntPtr 1 instance 00000000 _invocationCount 

Alors maintenant, je peux voir le pointeur sur la méthode qui n'est pas détaché

0040C060 _methodPtr 

mais comment puis-je obtenir le nom de cette méthode?

+1

S'il vous plaît voir ma réponse à cette question pour savoir comment obtenir ce que _methodPtr pointe vers http://stackoverflow.com/questions/3668642/get-method-name-form-delegate-with-windbg/3682594 # 3682594 –

+2

Wow, c'est une jolie tho explication grossière d'un examen de débogage. Upvote pour avoir pris le temps de poster un exemple détaillé - et il pourrait être utile aux autres à l'avenir. Je vais mettre ceci en signet. –

Répondre

1

Qu'en est-il de l'implémentation du bon vieux IDisposable?

 class TestMemoryLeak : IDisposable 
     { 
       public event EventHandler AnEvent; 
       private bool disposed = false; 

      internal TestMemoryLeak() 
      { 
       AnEventListener leak = new AnEventListener(); 
       this.AnEvent += (s, e) => leak.OnLeak(); 
       AnEvent(this, EventArgs.Empty); 
      } 

      protected virtual void Dispose(bool disposing) 
      { 
       if (!disposed) 
       { 
       if (disposing) 
       { 
         this.AnEvent -= (s, e) => leak.OnLeak(); 
       } 
       this.disposed = true; 
       } 

      } 

      public void Dispose() 
      { 
       this.Dispose(true); 
       GC.SupressFinalize(this); 
      } 

    } 
+0

Salut Max, merci pour la réponse. Ce serait cool dans cet exemple, mais dans la vraie application, il y a beaucoup de code et je ne connais pas le nom de la méthode à détacher, donc j'essaie de trouver ça ... – Gaz

+0

@Gaz : Mais vous pouvez utiliser ceci pour énumérer les délégués attachés à l'événement, et examiner la propriété 'Method' de chacun de ces délégués afin de savoir qui ne se détache pas. –

+0

@Jim Mischel: Pourriez-vous s'il vous plaît poster un code Jim? – Gaz

4

événements sont difficiles parce que quand a est abonné à B, à la fois finissent par la tenue d'une référence à l'autre. Dans votre exemple, ce n'est pas un problème car il n'y a pas de fuite (A est un B créé et est le seul objet à contenir une référence à B, donc A et B mourront quand A mourra).

Pour les problèmes d'événements réels, ce qui résoudrait le problème serait le concept des "événements faibles". Malheureusement, le seul moyen d'obtenir 100% d'événements faibles est le support du CLR. Microsoft semble n'avoir aucun intérêt à fournir ce support.

Je vous recommande de google "événements faibles en C#" et commencer à lire. Vous trouverez beaucoup d'approches différentes pour résoudre le problème, mais vous devez être conscient de leurs limites. Il n'y a pas de solution à 100%.

+0

Hey Tergiver, merci pour l'info. Je vais jeter un coup d'oeil aux événements faibles pour référence future. Le problème avec l'application sur laquelle je travaille est qu'il y a une classe de coordination qui est là pour la durée de vie de l'application. Et ce sont les événements de cette classe qui provoquent les fuites de mémoire, puisque tous les abonnés sont maintenus en vie en référence aux événements de cette classe de coordination. – Gaz

+0

Lorsque vous traitez des événements statiques sans un modèle d'événement faible, la seule option que vous avez est de vous désabonner avant que votre dernière référence ne disparaisse (après quoi vous n'avez aucun moyen de vous désabonner). – Tergiver

+0

Je devrais dire, "dernier * visible * référence". – Tergiver

1

expansion sur l'idée que IDisposable @Max Malygin proposé:

Le code ci-dessous montre comment vérifier les gestionnaires en cours sur un événement.

La classe a un événement Tick qui se déclenche une fois par seconde. Lorsque Dispose est appelée, le code énumère les gestionnaires dans la liste d'appel (s'il y en a) et affiche le nom de la classe et de la méthode encore souscrite à l'événement.

Le programme instancie un objet, attache un gestionnaire d'événements qui écrit "tick" chaque fois que l'événement est déclenché, puis dort pendant 5 secondes. Il dispose ensuite l'objet sans désabonner le gestionnaire d'événements.

using System; 
using System.Diagnostics; 
using System.Threading; 

namespace testo 
{ 
    public class MyEventThing : IDisposable 
    { 
     public event EventHandler Tick; 
     private Timer t; 

     public MyEventThing() 
     { 
      t = new Timer((s) => { OnTick(new EventArgs()); }, null, 1000, 1000); 
     } 

     protected void OnTick(EventArgs e) 
     { 
      if (Tick != null) 
      { 
       Tick(this, e); 
      } 
     } 

     ~MyEventThing() 
     { 
      Dispose(false); 
     } 

     public void Dispose() 
     { 
      Dispose(true); 
      GC.SuppressFinalize(this); 
     } 

     private bool disposed = false; 
     private void Dispose(bool disposing) 
     { 
      if (!disposed) 
      { 
       if (disposing) 
       { 
        t.Dispose(); 
        // Check to see if there are any outstanding event handlers 
        CheckHandlers(); 
       } 

       disposed = true; 
      } 
     } 

     private void CheckHandlers() 
     { 
      if (Tick != null) 
      { 
       Console.WriteLine("Handlers still subscribed:"); 
       foreach (var handler in Tick.GetInvocationList()) 
       { 
        Console.WriteLine("{0}.{1}", handler.Method.DeclaringType, handler.Method.Name); 
       } 
      } 
     } 

    } 

    class Program 
    { 
     static public long Time(Action proc) 
     { 
      Stopwatch sw = Stopwatch.StartNew(); 
      proc(); 
      return sw.ElapsedMilliseconds; 
     } 

     static int Main(string [] args) 
     { 
      DoIt(); 
      Console.WriteLine(); 
      Console.Write("Press Enter:"); 
      Console.ReadLine(); 
      return 0; 
     } 

     static void DoIt() 
     { 
      MyEventThing thing = new MyEventThing(); 
      thing.Tick += new EventHandler(thing_Tick); 
      Thread.Sleep(5000); 
      thing.Dispose(); 
     } 

     static void thing_Tick(object sender, EventArgs e) 
     { 
      Console.WriteLine("tick"); 
     } 
    } 
} 

La sortie est:

Handlers still subscribed: 
testo.Program.thing_Tick 
0

Vous pouvez essayer comme ça sur WinDbg

  1. Dump Obj cible pour obtenir la méthode Tableau: dumpobj 01e19248
  2. Table Méthode de vidage pour trouver 0040C060 dedans: ! dumpmt -md 0ced1910
  3. Si aucune correspondance, vider la mémoire qui commence à partir de l'adresse _methodPtr: u 0040C060
  4. Trouver instruction JMP ou MOVE et Dump leur adresse, par exemple: u 0cf54930

Visitez ici pour plus de détails: http://radheyv.blogspot.com/2011/04/detecting-memory-leaks-in-silverlight.html