c# applicationdomain

c# - ¿Por qué llamar a AppDomain.Unload no genera una recolección de basura?



applicationdomain (2)

Cuando realizo un AppDomain.Unload (myDomain) espero que también haga una recolección de basura completa.

Según Jeffrey Richter en "CLR via C #", dice que durante un AppDomain.Unload:

El CLR obliga a que se produzca una recolección de basura, reclamando la memoria utilizada por los objetos creados por el dominio de aplicación ahora descargado. Los métodos de Finalizar para estos objetos se llaman, lo que les da la oportunidad de limpiarse correctamente.

De acuerdo con "Steven Pratschner" en "Personalización de .NET Framework Common Language Runtime":

Una vez que todos los finalizadores se han ejecutado y no se están ejecutando más subprocesos en el dominio, el CLR está listo para descargar todas las estructuras de datos en memoria utilizadas en la implementación interna. Sin embargo, antes de que esto suceda, se deben recopilar los objetos que residían en el dominio. Después de que se produce la próxima recolección de basura, las estructuras de datos del dominio de la aplicación se descargan del espacio de direcciones del proceso y el dominio se considera descargado.

¿Estoy malinterpretando sus palabras? Hice la siguiente solución para reproducir el comportamiento inesperado (en .net 2.0 sp2):

Un proyecto de biblioteca de clases llamado "Interfaces" que contiene esta interfaz:

public interface IXmlClass { void AllocateMemory(int size); void Collect(); }

Un proyecto de biblioteca de clases llamado "ClassLibrary1" que hace referencia a "Interfaces" y contiene esta clase:

public class XmlClass : MarshalByRefObject, IXmlClass { private byte[] b; public void AllocateMemory(int size) { this.b = new byte[size]; } public void Collect() { Console.WriteLine("Call explicit GC.Collect() in " + AppDomain.CurrentDomain.FriendlyName + " Collect() method"); GC.Collect(); Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); } ~XmlClass() { Console.WriteLine("Finalizing in AppDomain {0}", AppDomain.CurrentDomain.FriendlyName); } }

Un proyecto de aplicación de consola que hace referencia al proyecto "Interfaces" y realiza la siguiente lógica:

static void Main(string[] args) { AssemblyName an = AssemblyName.GetAssemblyName("ClassLibrary1.dll"); AppDomain appDomain2 = AppDomain.CreateDomain("MyDomain", null, AppDomain.CurrentDomain.SetupInformation); IXmlClass c1 = (IXmlClass)appDomain2.CreateInstanceAndUnwrap(an.FullName, "ClassLibrary1.XmlClass"); Console.WriteLine("Loaded Domain {0}", appDomain2.FriendlyName); int tenmb = 1024 * 10000; c1.AllocateMemory(tenmb); Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); c1.Collect(); Console.WriteLine("Unloaded Domain{0}", appDomain2.FriendlyName); AppDomain.Unload(appDomain2); Console.WriteLine("Number of collections after unloading appdomain: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); Console.WriteLine("Perform explicit GC.Collect() in Default Domain"); GC.Collect(); Console.WriteLine("Number of collections: Gen0:{0} Gen1:{1} Gen2:{2}", GC.CollectionCount(0), GC.CollectionCount(1), GC.CollectionCount(2)); Console.ReadKey(); }

La salida cuando se ejecuta la aplicación de consola es:

Loaded Domain MyDomain Number of collections: Gen0:0 Gen1:0 Gen2:0 Call explicit GC.Collect() in MyDomain Collect() method Number of collections: Gen0:1 Gen1:1 Gen2:1 Unloaded Domain MyDomain Finalizing in AppDomain MyDomain Number of collections after unloading appdomain: Gen0:1 Gen1:1 Gen2:1 Perform explicit GC.Collect() in Default Domain Number of collections: Gen0:2 Gen1:2 Gen2:2

Cosas para notar:

  1. La recolección de basura se realiza por proceso (solo una actualización)

  2. Los objetos en el dominio de aplicación que se descargan tienen el finalizador llamado pero no se realiza la recolección de basura. El objeto de 10 megabytes creado por AllocateMemory () solo se recopilará después de realizar un GC.Collect () explícito en el ejemplo anterior (o si el recolector de basura lo hará en algún momento después).

Otras notas: realmente no importa si XmlClass es finalizable o no. El mismo comportamiento ocurre en el ejemplo anterior.

Preguntas:

  1. ¿Por qué llamar a AppDomain.Unload no genera una recolección de basura? ¿Hay alguna manera de hacer que esa llamada resulte en una recolección de basura?

  2. Dentro de AllocateMemory () planeo cargar documentos xml grandes de corta duración (menores o iguales a 16 mb) que se acumularán en el montón LargeObject y serán objetos de la generación 2. ¿Hay alguna forma de que la memoria se recopile sin tener que recurrir a GC.Collect () explícito u otro tipo de control programático explícito del recolector de basura?


  1. Probablemente por diseño, pero no entiendo por qué quiere este comportamiento (GC.Collect explícito). Mientras se llamen los finalizadores, los objetos se eliminan de la cola del finalizador y están listos para ser recolectados en la basura si es necesario (el hilo gc se activará cuando sea necesario).

  2. Probablemente pueda usar alguna asignación no administrada desagradable y alguna interoperabilidad pesada, o codificarla en c ++ no administrado y luego usar un contenedor administrado para acceder a ella a través de C #, pero mientras se mantenga dentro del mundo .Net administrado, no.

    Es más prudente echar un segundo vistazo a su arquitectura en lugar de centrarse en tratar de desempeñar el papel del recolector de basura.


Notas adicionales:

Después de un intercambio de correo con Jeffrey Richter, que tuvo la amabilidad de echar un vistazo a la pregunta:

OK, leí tu post.
Primero, la matriz no será GC''d hasta que el objeto XMLClass sea GC''d y se necesiten DOS GCs para recolectar este objeto porque contiene un método Finalizar.
En segundo lugar, la descarga de un dominio de aplicación realiza al menos la fase de marcado del GC, ya que esta es la única forma de determinar qué objetos son inalcanzables para poder llamar a sus métodos de Finalizar.
Sin embargo, la parte compacta del GC puede o no realizarse al descargar un GC. Llamar a GC.CollectionCount obviamente no cuenta toda la historia. No está demostrando que la fase de marcado GC ocurrió.
Y, es posible que AppDomain.Unload inicie un GC a través de algún código interno que no haga que las variables de conteo de la colección se incrementen. Ya sabemos a ciencia cierta que la fase de marcado se está realizando y que el recuento de la colección no refleja esto.

Una mejor prueba sería observar algunas direcciones de objetos en el depurador y ver si realmente se produce la compactación. Si lo hace (y sospecho que sí), entonces el recuento de la colección simplemente no se actualiza correctamente.

Si desea publicar esto en el sitio web como mi respuesta, puede hacerlo.

Después de seguir su consejo y buscar en SOS (que también eliminó el finalizador), reveló esto:

Antes de AppDomain.Unload:

!EEHeap -gc Number of GC Heaps: 1 generation 0 starts at 0x0180b1f0 generation 1 starts at 0x017d100c generation 2 starts at 0x017d1000 ephemeral segment allocation context: none segment begin allocated size 017d0000 017d1000 01811ff4 0x00040ff4(266228) Large object heap starts at 0x027d1000 segment begin allocated size 027d0000 027d1000 02f75470 0x007a4470(8012912) Total Size 0x7e5464(8279140) ------------------------------ GC Heap Size 0x7e5464(8279140)

Después de AppDomain.Unload (mismas direcciones, no se realizó compactación del montón)

!EEHeap -gc Number of GC Heaps: 1 generation 0 starts at 0x0180b1f0 generation 1 starts at 0x017d100c generation 2 starts at 0x017d1000 ephemeral segment allocation context: none segment begin allocated size 017d0000 017d1000 01811ff4 0x00040ff4(266228) Large object heap starts at 0x027d1000 segment begin allocated size 027d0000 027d1000 02f75470 0x007a4470(8012912) Total Size 0x7e5464(8279140) ------------------------------ GC Heap Size 0x7e5464(8279140)

Después de GC.Collect (), las direcciones difieren, lo que indica que se realizó la compactación del montón.

!EEHeap -gc Number of GC Heaps: 1 generation 0 starts at 0x01811234 generation 1 starts at 0x0180b1f0 generation 2 starts at 0x017d1000 ephemeral segment allocation context: none segment begin allocated size 017d0000 017d1000 01811ff4 0x00040ff4(266228) Large object heap starts at 0x027d1000 segment begin allocated size 027d0000 027d1000 027d3240 0x00002240(8768) Total Size 0x43234(274996) ------------------------------ GC Heap Size 0x43234(274996)

Después de más sos, la conclusión a la que he llegado es que es seguramente por diseño, y que la compactación del montón no se hace necesariamente. Lo único de lo que realmente puede estar seguro durante la descarga de un dominio de aplicación es que los objetos se marcarán como inaccesibles y se recolectarán durante la próxima recolección de basura (lo cual, como dije, no se realiza exactamente cuando descarga su dominio de aplicación, a menos que haya una coincidencia).

EDITAR: También le he preguntado a Maoni Stephens, que trabaja directamente en el equipo de GC. Puedes leer su respuesta en algún lugar de los comentarios here . Ella confirma que es por diseño. Caso cerrado :)