2010-11-20 16 views
13

Tengo una aplicación que tiene algunas pérdidas de memoria debido a eventos que no se separan antes de que una referencia de objeto se establezca en nulo. La aplicación es bastante grande y es difícil encontrar las pérdidas de memoria al mirar el código. Quiero usar sos.dll para encontrar los nombres de los métodos que son la fuente de las filtraciones, pero me estoy atascado. Configuré un proyecto de prueba para demostrar el problema.C# Fugas de memoria basadas en eventos

Aquí tengo 2 clases, una con un evento, y sobre las escuchas a ese evento de la siguiente manera

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

rompo en el código, y en el tipo de ventana intermedia

.load sos.dll 

luego utilizo! dumpheap para obtener los objetos en el montón de la AnEventListener tipo

!dumpheap -type MemoryLeak.AnEventListener 

y consigo el 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 

yo uso! gcroot de averiguar por qué el objeto no está siendo recolectado

!gcroot 01e19254 

y obtener la siguiente

!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 

Ahora puede ver el controlador de eventos que se la fuente de la fuga Yo uso! Hacer para mirar a los campos del controlador de eventos y obtener

!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 

Así que ahora puedo ver el puntero al método que no está siendo separada

0040C060 _methodPtr 

pero ¿Cómo llego el nombre de ese método?

+1

Por favor, consulte mi respuesta a esta pregunta para saber cómo obtener _methodPtr http://stackoverflow.com/questions/3668642/get-method-name-form-delegate-with-windbg/3682594 # 3682594 –

+2

Guau, esto es bastante bonito explicación aproximada de un examen de depuración. Upvote por tomarse el tiempo de publicar un ejemplo detallado, y puede que otros lo usen en el futuro. Voy a marcar esto. –

Respuesta

1

¿Qué hay de la implementación de la buena edad 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

Hola Max, gracias por la respuesta. Eso sería genial en este ejemplo, pero en la aplicación real, hay un montón de código y no sé el nombre del método que necesita separar, así que estoy tratando de averiguarlo ... – Gaz

+0

@Gaz : Pero puede usar esto para enumerar a los delegados adjuntos al evento, y examinar la propiedad 'Método 'de cada uno de esos delegados con el fin de averiguar quién no se está separando. –

+0

@Jim Mischel: ¿Podría publicar algún código Jim? – Gaz

4

eventos son difíciles porque cuando un suscribe a B, ambos terminan sosteniendo una referencia a unos de otros. En su ejemplo, esto no es un problema ya que no hay fuga (A creó B y es el único objeto que contiene una referencia a B, por lo que tanto A como B morirán cuando A muera).

Para problemas de eventos reales, lo que resolvería es el concepto de "eventos débiles". Desafortunadamente, la única forma de obtener eventos débiles que funcionen al 100% es con el soporte del CLR. Microsoft parece no tener interés en proporcionar este soporte.

Te recomiendo google "débiles eventos en C#" y comienza a leer. Encontrarás diferentes enfoques para resolver el problema, pero debes ser consciente de sus limitaciones. No hay una solución al 100%.

+0

Hola Tergiver, gracias por la información. Echaré un vistazo a los eventos débiles para referencia futura. El problema con la aplicación en la que estoy trabajando es que hay una clase coordinadora disponible durante toda la vida de la aplicación. Y son los eventos de esta clase los que están causando filtraciones de memoria, ya que los suscriptores se mantienen vivos por referencia a los eventos en esta clase de coordinación. – Gaz

+0

Cuando se trata de eventos estáticos sin un patrón de evento débil, la única opción que tiene es darse de baja antes de que desaparezca su última referencia (después de lo cual no tiene forma de darse de baja). – Tergiver

+0

Debo decir, "última * referencia * visible". – Tergiver

1

Ampliando la idea de que IDisposable @Max Malygin propuso:

El código siguiente muestra cómo comprobar si los controladores en circulación en un evento.

La clase tiene un evento Tick que se dispara una vez por segundo. Cuando se llama al Dispose, el código enumera los manejadores en la lista de invocación (si hay alguno) y emite la clase y el nombre del método que aún está suscrito al evento.

El programa crea una instancia de un objeto, adjunta un controlador de eventos que escribe "tic" cada vez que se dispara el evento, y luego duerme durante 5 segundos. A continuación, elimina el objeto sin anular la suscripción del controlador de eventos.

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 salida es:

Handlers still subscribed: 
testo.Program.thing_Tick 
0

Usted puede tratar como este en WinDbg

  1. volcado Objetivo Obj conseguir método de la tabla: dumpobj 01e19248
  2. Mesa de Descarga Método para encontrar 0040C060 en él: ! dumpmt -md 0ced1910
  3. Si hay ninguna coincidencia, volcado de la memoria que se inicia a partir de la dirección de _methodPtr: u 0040C060
  4. Encuentra instrucción JMP o mover y volcar su dirección, por ejemplo: u 0cf54930

Visita aquí para más detalles: http://radheyv.blogspot.com/2011/04/detecting-memory-leaks-in-silverlight.html