objectcache net example cache c# .net caching garbage-collection

c# - example - .Net 4 MemoryCache Leaks con recolección concurrente de basura



net core cache (4)

Estoy usando el nuevo MemoryCache en .Net 4, con un límite máximo de tamaño de caché en MB (lo he probado entre 10 y 200 MB, en sistemas con entre 1,75 y 8 GB de memoria). No establezco ninguna caducidad basada en el tiempo en los objetos, ya que estoy usando el caché simplemente como un disco de alto rendimiento, y mientras haya espacio, quiero que se use. Para mi sorpresa, el caché se negó a expulsar cualquier objeto, hasta el punto de que obtendría excepciones de SystemOutOfMemory .

Inicié perfmon , conecté mi aplicación a .Net CLR Memory/#Bytes In All Heaps , .Net Memory Cache 4.0 y Process/Private Bytes - de hecho, el consumo de memoria estaba fuera de control, y no se estaban realizando ajustes de caché registrado.

Hizo algunas búsquedas en Google y stackoverflowing , descargó y adjuntó el MemoryCache , y MemoryCache : desalojos en todas partes! La memoria se mantuvo dentro de límites razonables según el límite de tamaño de la memoria que había establecido. Lo ejecuté de nuevo en modo de depuración, sin desalojos. CLRProfiler nuevamente, desalojos.

Finalmente me di cuenta de que el generador de perfiles obligó a la aplicación a ejecutarse sin recolección de basura concurrente (también vea la pregunta útil de recolección concurrente de SO ). Lo apagué en mi app.config, y, efectivamente, ¡desalojos!

Esto parece en el mejor de los casos una falta escandalosa de documentación para no decir: esto solo funciona con la recolección no concurrente de basura : aunque la imagen desde su portado desde ASP.NET, es posible que no tengan que preocuparse por la recolección concurrente de basura .

Entonces, ¿alguien más ha visto esto? Me encantaría obtener algunas otras experiencias, y tal vez algunas ideas más educadas.

Actualización 1

He reproducido el problema con un solo método: parece que la caché debe escribirse en paralelo para que los desalojos de la memoria caché no se activen (en el modo de recolección de basura concurrente). Si hay algún interés, subiré el código de prueba a un repositorio público. Definitivamente estoy llegando al extremo profundo del grupo CLR / GC / MemoryCache, y creo que olvidé mis flotadores ...

Actualización 2

Publiqué el código de prueba en CodePlex para reproducir el problema. Además, posiblemente de interés, el código de producción original se ejecuta en Azure, como Rol de trabajador. Interesante, cambiar la configuración de concurrencia de GC en la aplicación app.config de la función no tiene ningún efecto. ¿Posiblemente Azure anula la configuración de GC como ASP.NET? Además, ejecutar el código de prueba en WPF frente a una aplicación de consola producirá resultados de desalojo ligeramente diferentes.


Encontré esta entrada mientras buscaba un tema similar y me estoy enfocando en su excepción de falta de memoria.

Si coloca un objeto en la memoria caché, puede seguir haciendo referencia a otros objetos y, por lo tanto, estos objetos no se recogerán. De ahí la excepción de falta de memoria y probablemente una CPU bloqueada debido a la recolección de basura Gen 2.

¿Está colocando objetos "usados" en el caché o clones de objetos "usados" en el caché? Si coloca un clon en la memoria caché, el objeto "usado" que pueda hacer referencia a otros objetos podría ser basura.

Si apaga su mecanismo de almacenamiento en caché, ¿su programa aún se queda sin memoria? Si no se queda sin memoria, eso probaría que los objetos que de lo contrario estarías poniendo en la memoria caché todavía tienen referencias a otros objetos que dificultan la recolección de basura.

Forzar la recolección de basura no es una buena práctica y no debería hacerse. En este escenario, forzar una recolección de basura no eliminaría los objetos referenciados de todos modos.


La recolección de basura mundial se basa en determinar si existe una referencia en vivo fuerte a un objeto en el momento en que se detiene el mundo. La recolección concurrente de basura generalmente determina si una referencia en vivo fuerte a un objeto ha existido desde algún tiempo particular en el pasado. Mi conjetura sería que muchas referencias fuertes a los objetos contenidos en WeakReferences se crean y descartan individualmente. Si un recolector de basura que se detiene en el mundo dispara entre el momento en que se crea un objeto en particular y el momento en que se descarta, ese objeto en particular se mantendrá con vida, pero los objetos descartados anteriormente no se guardarán. Por el contrario, un recolector de basura simultáneo puede no detectar que todas las referencias sólidas de un objeto hayan sido descartadas hasta que pase una cierta cantidad de tiempo sin referencias fuertes a la creación de ese objeto.

A veces he deseado que .net ofrezca algo entre una referencia fuerte y una débil, lo que evitaría que un objeto se borre de la memoria, pero no lo protegería de que se finalizara o de que se invalidaran WeakReferences débiles. Dichas referencias complicarían ligeramente el proceso del GC, requiriendo que cada objeto tenga banderas separadas que indiquen si existen referencias fuertes y cuasi-débiles, y si se ha escaneado para referencias fuertes y cuasi débiles, pero tal característica podría ser útil. en muchos escenarios de "eventos débiles".


MemoryCache definitivamente tiene algunos problemas. Comió 160Mb de memoria en mi servidor asp.net, simplemente cambió a una lista simple y agregó un poco de lógica extra para obtener la misma funcionalidad.


Puede "forzar" una recolección de basura justo después del método problemático y ver si el problema reproduce la ejecución:

System.Threading.Thread.Sleep(200); GC.Collect(); GC.WaitForPendingFinalizers();

justo al final del método (asegúrese de liberar cualquier asa para hacer referencia a los objetos y anularlos). Si esto impide la pérdida de memoria, y luego sí, puede haber un error de tiempo de ejecución.