2010-11-20 2 views
13

Mam aplikację, która ma pewne wycieki pamięci ze względu na to, że zdarzenia nie są odłączane, zanim odwołanie do obiektu jest ustawione na wartość null. Wniosek jest dość duży i trudno jest znaleźć wycieki pamięci, patrząc na kod. Chcę użyć sos.dll, aby znaleźć nazwy metod, które są źródłem przecieków, ale utknąłem. Przygotowałem projekt testowy, aby zademonstrować problem.C# Wady pamięci oparte na zdarzeniach

Tutaj mam 2 klas, jeden ze zdarzeniem, a na słucha tego wydarzenia poniżej

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"); 
     } 
    } 
} 

złamię do kodu, w pośredniej okna typu

.load sos.dll 

następnie używam! dumpheap, aby uzyskać obiekty na stercie typu AnEventListener

!dumpheap -type MemoryLeak.AnEventListener 

i otrzymuję 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 

używam! gcroot wypracować dlaczego obiekt nie jest śmieci zbierane

!gcroot 01e19254 

i uzyskać następujące

!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 

mogę teraz zobaczyć obsługi zdarzeń, który jest źródło wycieku. Używam! Zrobić, aby spojrzeć na dziedzinie obsługi zdarzeń i dostać

!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 

Więc teraz widzę wskaźnik do metody, która nie jest oderwane

0040C060 _methodPtr 

ale jak mogę dostać nazwa tej metody?

+1

Proszę zobaczyć moją odpowiedź na to pytanie, jak uzyskać to, co _methodPtr wskazuje na http://stackoverflow.com/questions/3668642/get-method-name-form-delegate-w-windbg/3682594 # 3682594 –

+2

Wow, to jest piękne przybliżone objaśnienie badania debugowania. Weź udział w zajęciach, aby poświęcić trochę czasu na opublikowanie szczegółowego przykładu - i może on zostać wykorzystany w przyszłości przez innych. Zakładam to. –

Odpowiedz

1

Co z wdrażaniem dobrego starego 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

Cześć Max, dziękuję za odpowiedź. W tym przykładzie byłoby to fajne, ale w prawdziwej aplikacji jest dużo kodu i nie znam tej nazwy metody, która wymaga odłączenia, więc próbuję ją znaleźć ... – Gaz

+0

@Gaz : Ale możesz użyć tego do wyliczenia delegatów dołączonych do wydarzenia i zbadać właściwość "Metody" każdego z tych delegatów, aby dowiedzieć się, kto się nie odłącza. –

+0

@Jim Mischel: Czy mógłbyś napisać jakiś kod Jim? – Gaz

4

wydarzenia są trudne, ponieważ gdy a subskrybuje B, zarówno skończyć posiadających odniesienie do siebie. W twoim przykładzie nie stanowi to problemu, ponieważ nie ma przecieku (A stworzony B i jest jedynym obiektem, który ma referencję do B, więc zarówno A, jak i B umrą, gdy A umiera).

W przypadku prawdziwych problemów związanych z wydarzeniami, rozwiązaniem byłaby koncepcja "słabych zdarzeń". Niestety, jedynym sposobem uzyskania 100% słabych zdarzeń jest wsparcie z CLR. Microsoft wydaje się nie być zainteresowany udzielaniem tego wsparcia.

Polecam Ci "słabe zdarzenia w C#" w Google i zacznij czytać. Znajdziesz wiele różnych podejść do rozwiązania problemu, ale musisz zdawać sobie sprawę z ich ograniczeń. Nie ma 100% roztworu.

+0

Hej Tergiver, dzięki za informację. Przyjrzyj się słabym zdarzeniom na przyszłość. Problem z aplikacją, nad którą pracuję, polega na tym, że istnieje klasa koordynująca, która jest dostępna przez cały okres użytkowania aplikacji. I właśnie zdarzenia na tej klasie powodują wycieki pamięci, ponieważ wszyscy subskrybenci utrzymują się przy życiu w odniesieniu do zdarzeń na tej klasie koordynującej. – Gaz

+0

W przypadku występowania zdarzeń statycznych bez słabego wzorca zdarzeń, jedyną dostępną opcją jest rezygnacja z subskrypcji przed zniknięciem ostatniego odwołania (po którym to momencie nie można się wypisać). – Tergiver

+0

Powinienem powiedzieć "ostatni * widoczny * punkt odniesienia". – Tergiver

1

Rozszerzenie na IDisposable idei, że @Max Malygin zaproponowanego:

Poniższy kod pokazuje jak sprawdzić za wybitne koparki na zdarzenie.

Klasa ma zdarzenie Tick, które jest uruchamiane raz na sekundę. Po wywołaniu Dispose kod wylicza procedury obsługi na liście wywołań (jeśli istnieją) i wypisuje nazwę klasy i metody, które wciąż subskrybują zdarzenie.

Program tworzy instancję obiektu, dołącza procedurę obsługi zdarzenia, która wypisze "tick" za każdym razem, gdy zdarzenie zostanie uruchomione, a następnie przełączy się na 5 sekund. Następnie zrzuca obiekt bez anulowania subskrypcji obsługi zdarzenia.

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"); 
     } 
    } 
} 

Wyjście jest:

Handlers still subscribed: 
testo.Program.thing_Tick 
0

Można spróbować tak na WinDbg

  1. Dump docelowa Obj dostać Method Tabela:! dumpobj 01e19248
  2. Dump Method Table znaleźć 0040C060 w nim: ! dumpmt -md 0ced1910
  3. Jeśli nie pasuje, zrzutu pamięci, które zaczynają się od adresu _methodPtr! u 0040C060
  4. Znajdź instrukcji JMP lub przenieść i zrzucić swój adres, np: u 0cf54930

Wizyta tutaj po więcej szczegółów: http://radheyv.blogspot.com/2011/04/detecting-memory-leaks-in-silverlight.html