2011-05-24 10 views
9

Tengo una variable de bucle que no parece estar recibiendo basura recolectada (de acuerdo con el perfilador de memoria ANTS Red - Gate) a pesar de haber salido del alcance.Variable de bucle que no se recopila

El código es como la siguiente:

while (true) 
{ 
    var item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue 
    // do something with item 
} 

Por lo que yo puedo decir, una referencia a item permanece hasta blockingQueue.dequeue() devoluciones. ¿Es este el comportamiento previsto, o podría ser un error en el generador de perfiles de memoria?

En segundo lugar, si este es el comportamiento previsto, ¿cómo forzaría item para recoger en el extremo del cuerpo del bucle? Establecerlo en null no parece hacer que se recopile. Esto es importante ya que la cola podría bloquearse durante un tiempo prolongado y item hace referencia a un árbol de objetos bastante grande.

Nota: la documentación del generador de perfiles indica que se realiza un GC antes de tomar una instantánea de memoria y que la referencia no se encuentra en la cola del finalizador.

Pude reproducir el mismo problema con el código here.

actualización

El código en lo esencial fue un poco defectuoso, ya que legítimamente se aferró a una referencia en GetFoo(). Después de haberlo cambiado, el objeto hace ahora se recoge cuando se establece explícitamente en null. Sin embargo, creo que la respuesta de Hans explica la situación que estoy viendo en mi código actual.

+3

hace otra cosa tienen una referencia a él? –

+0

¿Puede mostrar todos los usos del artículo? –

+0

El generador de perfiles muestra referencias al objeto de las raíces de GC y este es uno de ellos. El ejemplo que publiqué en github muestra el problema y allí la referencia * solo * está dentro del ciclo. – SimonC

Respuesta

7

El optimizador de jitter es la fuente probable de este problema. He aquí un ejemplo:

class Program { 
    static void Main(string[] args) { 
     while (true) { 
      var input = Console.ReadLine(); 
      Console.WriteLine(input); 
      input = null; 
     } 
    } 
} 

genera este código de máquina:

  while (true) { 
       var input = Console.ReadLine(); 
00000000 push  ebp     ; setup stack 
00000001 mov   ebp,esp 
00000003 push  esi 
00000004 call  6E0208F0    ; Console.In property getter 
00000009 mov   ecx,eax 
0000000b mov   eax,dword ptr [ecx] 
0000000d call  dword ptr [eax+64h] ; TextReader.ReadLine() 
00000010 mov   esi,eax    ; assign input variable 
       Console.WriteLine(input); 
00000012 call  6DB7BE38    ; Console.Out property getter 
00000017 mov   ecx,eax 
00000019 mov   edx,esi 
0000001b mov   eax,dword ptr [ecx] 
0000001d call  dword ptr [eax+000000D8h] ; TextWriter.WriteLine() 
00000023 jmp   00000004    ; repeat, note the missing null assigment 

ESI registro almacena la variables entrada. Tenga en cuenta que nunca se vuelve a poner nulo, siempre almacena una referencia a la última cadena ingresada. El optimizador ha eliminado la declaración de asignación nula. El recolector de basura obtiene pistas de por vida del jitter, dirá que la referencia es en vivo durante la duración del ciclo.

El problema ocurre en el segundo pase y el siguiente, cuando nunca escribe algo, ReadLine() bloqueará (similar a su cola de bloqueo) y el valor de registro esi continuará haciendo referencia a la cadena. Nunca será basura recolectada durante la duración del ciclo, al menos hasta que se reasigne.

No hay una solución limpia para esto. He aquí un feo:

[MethodImpl(MethodImplOptions.NoInlining)] 
    public static void NullReference<T>(ref T obj) where T : class { 
     obj = null; 
    } 

y uso:

 while (true) { 
      var input = Console.ReadLine(); 
      Console.WriteLine(input); 
      NullReference(ref input); 
     } 
1

Hasta llamar al Dequeue, entonces el valor del artículo no se ha sobrescrito y todavía está en uso ¿es correcto? Lo mejor que puede hacer es configurarlo en nulo, la llamada GC.Collect(), pero no se garantiza que tenga esa variable recopilada, y no hay forma de obligarla a recopilarla, entonces, ¿para qué molestarse?

+0

Me molesto porque en mi causa la dequeue puede llevar mucho tiempo y el elemento contiene referencias a un árbol de objetos grande. Lo mejor que puedo hacer en este punto es llamar a 'item.Dispose()' y espero que elimine las referencias a sus objetos allí (la clase a la que se hace referencia es un componente GUI de un tercero, por lo que no tengo control sobre él). – SimonC

0
while (true) 
{ 
    { 
     var item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue 
     // do something with item 
    } 
    // do others that might be blocking for a long time 
} 

Sospecho que adjuntarlo en un bloque podría funcionar. Si es desechable, usted podría

while (true) 
{ 
    using (var item = blockingQueue.dequeue(); 
    { 
     // do something with item 
    } 
    // do others that might be blocking for a long time 
} 

Tal vez lo haya entendido mal, pero aquí hay otra posibilidad de manejar otra situación:

while (true) 
{ 
    var item = null; 
    item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue 
    // do something with item 
    item = null; 
} 
0

Si ha terminado con el artículo, usted puede liberar su referencia a ella al final del cuerpo del bucle:

item = null; 

en cuanto a la basura recogida de ella, no importa lo grande que es objeto, si no hay otras referencias a ella y el recolector de basura no tiene c lo olvidó, entonces el recolector de basura no cree que deba ser recogido todavía.

Deje que el recolector de basura haga su trabajo. Recolectará cosas a su debido tiempo y lo hará de manera eficiente, intercambiando memoria y tiempo.

0

Creo que el problema es que item nunca sale del alcance hasta que termina el ciclo. El GC no es lo suficientemente inteligente como para reconocer que el valor en item no se va a utilizar antes de que se sobrescriba, por lo que no puede recopilarlo.

Simplemente configurándolo en null cuando haya terminado eliminará la última referencia y permitirá que su objeto sea recolectado.

0

bien los dos siguiente código tijeras produce el mismo il:

int i = 0; 
System.Object x; 
while(i < 100){ 
    x = new System.Object(); 
    System.Console.WriteLine(x.ToString()); 
    i++; 
} 

Ahora tratando de depender de ámbito léxico para liberar el árbitro local en x:

int i = 0; 
while(i < 100){ 
    System.Object x = new System.Object(); 
    System.Console.WriteLine(x.ToString()); 
    i++; 
} 

El resultado es el mismo en ambos casos. El local que contiene la referencia llamada x no se anula cuando termina una iteración del ciclo. Incluso si no nos ramificamos al inicio del bucle, el local es nunca establecido en nulo. En cambio, el compilador reutilizará esta ranura de variable local cuando surja una oportunidad.

Si establece explícitamente x en nulo, el compilador emitirá il para establecer el local en nulo, incluso si tiene indicadores de optimización activados. Si eso se optimiza, no está compilando estática en el JIT.

Cuestiones relacionadas