Considere el siguiente código:
public class Class1
{
public static int c;
~Class1()
{
c++;
}
}
public class Class2
{
public static void Main()
{
{
var c1=new Class1();
//c1=null; // If this line is not commented out, at the Console.WriteLine call, it prints 1.
}
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Class1.c); // prints 0
Console.Read();
}
}
Ahora, aunque la variable c1 en el método principal está fuera del alcance y ningún otro objeto hace referencia a ella cuando GC.Collect()
se llama, ¿por qué no se finaliza allí?
c#
.net
garbage-collection
Victor Mukherjee
fuente
fuente
Respuestas:
Te están tropezando aquí y sacas conclusiones muy erróneas porque estás usando un depurador. Tendrá que ejecutar su código de la forma en que se ejecuta en la máquina de su usuario. Cambie a la versión Release primero con Build + Configuration manager, cambie el combo "Configuración de solución activa" en la esquina superior izquierda a "Release". A continuación, vaya a Herramientas + Opciones, Depuración, General y desactive la opción "Suprimir la optimización de JIT".
Ahora ejecute su programa nuevamente y juegue con el código fuente. Tenga en cuenta que las llaves adicionales no tienen ningún efecto. Y observe cómo establecer la variable en nulo no hace ninguna diferencia. Siempre imprimirá "1". Ahora funciona de la manera que esperaba y esperaba que funcionara.
Lo que deja con la tarea de explicar por qué funciona tan diferente cuando ejecuta la compilación de depuración. Eso requiere explicar cómo el recolector de basura descubre las variables locales y cómo se ve afectado por tener un depurador presente.
En primer lugar, la fluctuación de fase realiza dos tareas importantes cuando compila el IL para un método en código máquina. El primero es muy visible en el depurador, puede ver el código de la máquina con la ventana Depuración + Windows + Desmontaje. Sin embargo, el segundo deber es completamente invisible. También genera una tabla que describe cómo se usan las variables locales dentro del cuerpo del método. Esa tabla tiene una entrada para cada argumento de método y variable local con dos direcciones. La dirección donde la variable almacenará primero una referencia de objeto. Y la dirección de la instrucción de código de máquina donde esa variable ya no se usa. También si esa variable se almacena en el marco de la pila o en un registro de la CPU.
Esta tabla es esencial para el recolector de basura, necesita saber dónde buscar referencias de objetos cuando realiza una recolección. Es bastante fácil de hacer cuando la referencia es parte de un objeto en el montón de GC. Definitivamente no es fácil de hacer cuando la referencia del objeto se almacena en un registro de la CPU. La mesa dice dónde mirar.
La dirección "ya no se usa" en la tabla es muy importante. Hace que el recolector de basura sea muy eficiente . Puede recopilar una referencia de objeto, incluso si se usa dentro de un método y ese método aún no ha terminado de ejecutarse. Lo cual es muy común, su método Main (), por ejemplo, solo dejará de ejecutarse justo antes de que finalice su programa. Claramente, no querrá que ninguna referencia de objeto utilizada dentro de ese método Main () viva durante la duración del programa, eso equivaldría a una fuga. El jitter puede usar la tabla para descubrir que dicha variable local ya no es útil, dependiendo de cuánto haya progresado el programa dentro de ese método Main () antes de realizar una llamada.
Un método casi mágico relacionado con esa tabla es GC.KeepAlive (). Es un método muy especial, no genera ningún código en absoluto. Su único deber es modificar esa tabla. Se extiendela vida útil de la variable local, evitando que la referencia que almacena se recolecte basura. El único momento en que necesita usarlo es evitar que el GC esté demasiado ansioso por recopilar una referencia, lo que puede suceder en escenarios de interoperabilidad en los que se pasa una referencia a código no administrado. El recolector de basura no puede ver tales referencias siendo utilizadas por dicho código ya que no fue compilado por el jitter, por lo que no tiene la tabla que dice dónde buscar la referencia. Pasar un objeto delegado a una función no administrada como EnumWindows () es el ejemplo de cuando necesitas usar GC.KeepAlive ().
Entonces, como puede deducir de su fragmento de muestra después de ejecutarlo en la compilación de lanzamiento, las variables locales se pueden recopilar temprano, antes de que el método termine de ejecutarse. Aún más poderoso, un objeto puede ser recolectado mientras uno de sus métodos se ejecuta si ese método ya no se refiere a esto . Hay un problema con eso, es muy incómodo depurar dicho método. Ya que puede poner la variable en la ventana Observar o inspeccionarla. Y desaparecería mientras está depurando si se produce un GC. Eso sería muy desagradable, por lo que la inquietud es consciente de que hay un depurador adjunto. Luego modificala tabla y altera la dirección del "último uso". Y lo cambia de su valor normal a la dirección de la última instrucción del método. Lo que mantiene viva la variable mientras el método no haya regresado. Lo que le permite seguir viéndolo hasta que el método regrese.
Esto ahora también explica lo que viste antes y por qué hiciste la pregunta. Imprime "0" porque la llamada GC.Collect no puede recopilar la referencia. La tabla dice que la variable está en uso después de la llamada GC.Collect (), hasta el final del método. Obligado a decir eso al tener el depurador conectado y al ejecutar la compilación de depuración.
Establecer la variable en nulo tiene un efecto ahora porque el GC inspeccionará la variable y ya no verá una referencia. Pero asegúrese de no caer en la trampa en la que muchos programadores de C # han caído, en realidad escribir ese código no tenía sentido. No importa en absoluto si esa declaración está presente o no cuando ejecuta el código en la compilación de lanzamiento. De hecho, el optimizador de jitter eliminará esa declaración ya que no tiene ningún efecto. Así que asegúrese de no escribir código como ese, aunque parezca tener un efecto.
Una nota final sobre este tema, esto es lo que causa problemas a los programadores que escriben pequeños programas para hacer algo con una aplicación de Office. El depurador generalmente los pone en el camino equivocado, quieren que el programa de Office salga a pedido. La forma adecuada de hacerlo es llamando a GC.Collect (). Pero descubrirán que no funciona cuando depuran su aplicación, llevándolos a la tierra de nunca jamás llamando a Marshal.ReleaseComObject (). Gestión de memoria manual, rara vez funciona correctamente porque pasarán por alto fácilmente una referencia de interfaz invisible. GC.Collect () realmente funciona, pero no cuando depura la aplicación.
fuente
<Optimize>true</Optimize>
in.csproj
) está habilitado. Este es el valor predeterminado en la configuración "Release". Pero en caso de que se usen configuraciones personalizadas, es relevante saber que esta configuración es importante.[Solo quería agregar más sobre el proceso interno de finalización]
Por lo tanto, crea un objeto y cuando se recoge el objeto,
Finalize
se debe llamar al método del objeto . Pero hay más en la finalización que esta simple suposición.BREVE CONCEPTOS ::
Objetos que NO implementan
Finalize
métodos, la memoria se recupera de inmediato, a menos que, por supuesto, ya no sean alcanzables porcódigo de aplicación
Implementar objetos de
Finalize
método, el concepto / Implementación deApplication Roots
,Finalization Queue
,Freacheable Queue
viene antes de que puedan ser recuperados.Cualquier objeto se considera basura si NO es alcanzable por el Código de aplicación
Supongamos que: las clases / objetos A, B, D, G, H NO implementan el
Finalize
Método y C, E, F, I, J implementan elFinalize
Método.Cuando una aplicación crea un nuevo objeto, el nuevo operador asigna la memoria del montón. Si el tipo del objeto contiene un
Finalize
método, se coloca un puntero al objeto en la cola de finalización .por lo tanto, los punteros a los objetos C, E, F, I, J se agregan a la cola de finalización.
La cola de finalización es una estructura de datos interna controlada por el recolector de basura. Cada entrada en la cola apunta a un objeto que debería tener su
Finalize
método llamado antes de que la memoria del objeto pueda ser reclamada. La siguiente figura muestra un montón que contiene varios objetos. Algunos de estos objetos son accesibles desde las raíces de la aplicación, y algunos no lo son. Cuando se crearon los objetos C, E, F, I y J, el marco .Net detecta que estos objetos tienenFinalize
métodos y se agregan punteros a estos objetos a la cola de finalización .Cuando se produce un GC (primera colección), se determina que los objetos B, E, G, H, I y J son basura. Debido a que A, C, D, F todavía son alcanzables por el Código de Aplicación representado a través de las flechas del Cuadro amarillo arriba.
El recolector de basura explora la cola de finalización buscando punteros a estos objetos. Cuando se encuentra un puntero, el puntero se elimina de la cola de finalización y se agrega a la cola alcanzable ("F-alcanzable").
La cola alcanzable es otra estructura de datos interna controlada por el recolector de basura. Cada puntero en la cola alcanzable identifica un objeto que está listo para que se
Finalize
llame su método.Después de la colección (1st Collection), el montón administrado se parece a la figura siguiente. La explicación dada a continuación ::
1.) La memoria ocupada por los objetos B, G y H se ha reclamado de inmediato porque estos objetos no tenían un método de finalización que debiera llamarse .
2.) Sin embargo, la memoria ocupada por los objetos E, I y J no se pudo recuperar porque su
Finalize
método aún no se ha llamado. La llamada al método Finalize se realiza mediante una cola freacheable.3.) A, C, D, F todavía son alcanzables por el Código de Aplicación representado a través de flechas del recuadro amarillo arriba, por lo que NO se recopilarán en ningún caso
Hay un hilo de ejecución especial dedicado a llamar a los métodos Finalizar. Cuando la cola alcanzable está vacía (que suele ser el caso), este subproceso duerme. Pero cuando aparecen las entradas, este hilo se activa, elimina cada entrada de la cola y llama al método Finalizar de cada objeto. El recolector de basura compacta la memoria recuperable y el subproceso especial de tiempo de ejecución vacía la cola alcanzable , ejecutando el
Finalize
método de cada objeto . Entonces, aquí finalmente es cuando se ejecuta su método FinalizeLa próxima vez que se invoca el recolector de basura (2da colección), ve que los objetos finalizados son realmente basura, ya que las raíces de la aplicación no lo señalan y la cola freachable ya no lo señala (también está VACÍO), por lo tanto, el la memoria para los objetos (E, I, J) simplemente se recupera de Heap. Vea la figura a continuación y compárela con la figura justo arriba
Lo importante a entender aquí es que se requieren dos GC para recuperar la memoria utilizada por los objetos que requieren finalización . En realidad, incluso se requieren más de dos colecciones, ya que estos objetos pueden promoverse a una generación anterior
NOTA :: La cola alcanzable se considera una raíz al igual que las variables globales y estáticas son raíces. Por lo tanto, si un objeto está en la cola alcanzable, entonces el objeto es accesible y no es basura.
Como última nota, recuerde que la aplicación de depuración es una cosa, Garbage Collection es otra cosa y funciona de manera diferente. Hasta ahora no puede SENTIR la recolección de basura simplemente depurando aplicaciones, más aún si desea investigar Memoria, comience aquí.
fuente