Aspnetcore: Asp.net Core no recolecta basura

Creado en 21 mar. 2017  ·  136Comentarios  ·  Fuente: dotnet/aspnetcore

No puedo entender por qué el núcleo de Asp.net no parece estar recolectando basura. La semana pasada dejé que un servicio web se ejecutara durante unos días y mi uso de memoria alcanzó los 20 GB. GC no parece estar funcionando correctamente. Entonces, para probar esto, escribí un método web muy simple que devuelve una gran colección de cadenas. La aplicación comenzó usando solo 124 MB, pero cada vez que invocaba el método web, el uso de la memoria aumentaba cada vez más hasta alcanzar los 411 MB. Hubiera subido más si hubiera seguido llamando al método web. Pero decidí dejar de probar.

Mi código de prueba fue este:

`

[HttpObtener]
Tarea asíncrona pública> TestGC() {
const string mensaje = "PRUEBA";
return Enumerable.Repeat(mensaje, 100000000);
}
`

Aunque podría estar pasando por alto algo... Según tengo entendido, el uso de la memoria no debería aumentar con cada llamada a este método. Después de crear el objeto y enviarlo a la interfaz de usuario, la memoria debería haberse liberado.

Como puede ver en la captura de pantalla a continuación, incluso después de que se llamara al GC, la memoria no se liberó.
netcorescreenshot

¡Gracias por la ayuda!
Arrendajo

investigate

Comentario más útil

¿Alguna novedad de esto? Estoy usando Core 2 sobre Net Framework y esto sigue sucediendo. Cada llamada a un _Controller_ aumentaba la memoria utilizada, pero nunca bajaba. (_Utilicé la Plantilla WebApi_)

Todos 136 comentarios

@rynowak

Aunque podría estar pasando por alto algo... Según tengo entendido, el uso de la memoria no debería aumentar con cada llamada a este método. Después de crear el objeto y enviarlo a la interfaz de usuario, la memoria debería haberse liberado.

Es probable que el gran montón de objetos te esté mordiendo aquí. Si asigna objetos de más de 85 KB de tamaño, se colocará en el LOH y se compactará muy raramente. Consulte http://stackoverflow.com/questions/8951836/why-large-object-heap-and-why-do-we-care para obtener más detalles (o https://github.com/dotnet/coreclr/blob/master /Documentation/botr/garbage-collection.md#design-of-allocator si desea profundizar).

No puedo entender por qué el núcleo de Asp.net no parece estar recolectando basura. La semana pasada dejé que un servicio web se ejecutara durante unos días y mi uso de memoria alcanzó los 20 GB.

¿Qué está haciendo su servicio que está creando objetos tan grandes? Debe intentar realizar un volcado de memoria antes de que sea demasiado grande, eso le mostrará claramente qué objetos se quedan y por qué se retienen (puede usar Visual Studio para ver volcados o una herramienta más avanzada como windbg o perfview).

Intente asignar la matriz desde el principio en lugar de llamar a Enumerable.Repeat
o compacte la memoria usando GCSettings.LargeObjectHeapCompactionMode (compatible con .Net Standard)

Gracias @davidfowl y @MhAllan por las respuestas. Pero este ejemplo fue artificial. Solo quería algo que usara una cantidad notable de memoria para poder tomar una captura de pantalla. Lo cierto es que esto está pasando con cualquier aplicación sin importar el tamaño del objeto en cuestión. Y para responder a su pregunta @davidfowl , mi servicio simplemente extraía algunos datos de la base de datos con dapper y devolvía el objeto resultante. Era una fila de datos para cada llamada. Así que tomó algunos días para que la memoria creciera a esa cantidad. En realidad, estaba tratando de probar la base de datos cuando me topé con esta peculiaridad. Había escrito una pequeña aplicación de consola y seguía llamando al método una y otra vez.

@zorthgo ¿ seguro que no es Dapper? Si crea sus scripts inyectando parámetros directamente en sus scripts SQL como en PHP, terminará con muchos scripts SQL almacenados en caché. Así es como lo hace Dapper
https://github.com/StackExchange/Dapper/blob/fe5c270aceab362c936456087a830e6fe1603cac/Dapper/SqlMapper.cs
Debe usar un generador de perfiles de memoria para saber qué mantiene las referencias a la memoria asignada. Visual Studio 2017 debería poder ayudarlo a tomar algunas instantáneas de la memoria antes y después de varias llamadas a su aplicación y compararlas.

@zorthgo Sí, también he visto esto. Estaba usando servicestack en mi aplicación de consola .net core y cada vez que hago una llamada a la API, el uso de memoria aumenta en cantidades masivas de 50 mb. Supuse que era un error VS2017, pero luego confirmé el alto uso en el administrador de tareas. Como dijo zorthgo, simplemente haciendo llamadas simples a la API, el uso de la memoria aumenta significativamente y no parece liberar memoria.

¿Esto solo sucede en ASP.NET Core en .NET Core o también es un problema con ASP.NET Core en .NET Framework?

¿Puede escribir una aplicación similar usando MVC 5 (en System.Web) y verificar que no ve el mismo comportamiento?

No puedo hacer ningún progreso de este problema en su estado actual.

En mi caso, estaba usando solo el marco de destino .NetCoreApp 1.1 y mi aplicación de consola hacía referencia a un modelo de objeto en un proyecto compartido.

No estoy seguro si esto ayudará a alguien. una aplicación de muestra que llama a hellorequest. En esta aplicación, la memoria de inicio es de 85 mb, luego, por solicitud repetitiva, logré aumentar el uso de la memoria a aproximadamente 145 mb. A veces vuelve a caer a 125 MB, pero luego se queda allí. No estoy seguro de si este es un comportamiento normal, ya que no estoy acostumbrado a las aplicaciones de consola .Net Core. Siempre supuse que estaba haciendo algo mal o que no creaba una instancia correctamente.

https://drive.google.com/open?id=0B0Gm-m-z_U84TU5keWFTMzc1ZWc

Enfrentando el mismo problema aquí en una aplicación Asp.Net Core implementada en producción con 3000-5000 usuarios activos... la memoria en el servidor aumentó a 15 GB ayer... tuve que configurar IIS para reciclar el AppPool cada 3 horas mientras todavía tratar de averiguar cuál es el problema.

¿Alguien hizo un volcado de memoria y miró lo que está ocupando toda la memoria en sus aplicaciones particulares?

@davidfowl @Pinox
Estoy trabajando en una gran empresa y queremos comenzar un nuevo proyecto con ASP.NET Core, pero cuando vi este problema, tenía miedo: preocupado:. este es un problema crítico y puede bloquear el ciclo de vida de nuestro proyecto.

Entonces, por favor, ¿está relacionado con ASP.NET Core o .NET Core (CoreCLR)? nos enfocaremos en Full .NET (4.6), por eso pregunto.

@ikourfaln en mi caso, estaba usando la aplicación de consola .net core, servicestack (versión .net core) y kestrel. Lo extraño es que el uso de la memoria sube a un nivel, luego se detiene repentinamente y no vuelve a subir. Supongo que lo mejor es probarlo de su lado con una muestra pequeña y verificar el comportamiento.

Tal vez @zorthgo pueda verificar por su parte si ve un comportamiento similar en esa memoria que se usa hasta cierto nivel y luego deja de aumentar, ya que ese es el comportamiento que estoy viendo. Actualicé mi aplicación de muestra para incluir el ejemplo de @zorthgo y no veo que la memoria se esté agotando. Sube pero finalmente se detiene.

Cambié la fuente ligeramente:

objeto público Cualquiera (solicitud TestGC)
{
const string mensaje = "PRUEBA";
return Enumerable.Repeat(mensaje, 100000);
}

@Pinox
Gracias, comprobaré el comportamiento de mi lado.

¿Qué hay de este error en 2.0?

¿Alguna novedad de esto? Estoy usando Core 2 sobre Net Framework y esto sigue sucediendo. Cada llamada a un _Controller_ aumentaba la memoria utilizada, pero nunca bajaba. (_Utilicé la Plantilla WebApi_)

Hola,

Tengo el mismo problema con ASP.NET Core 2. Tomé un volcado de memoria e intenté analizar. Por lo que veo, el problema es exactamente como dijo el OP. Mi aplicación comienza con una asignación de aproximadamente 75 MB, y muy rápidamente llega hasta ~ 750 MB, de los cuales 608 MB son "Memoria no utilizada asignada a .NET".

Primera instantánea al inicio de la aplicación:
image

Segunda instantánea después de 3 minutos y 100 solicitudes:
image

También nos enfrentamos al mismo problema, nuestro controlador está manejando una gran cantidad de datos (que es un mal diseño y se reemplazará pronto), cada llamada a este controlador hace que la memoria crezca. La memoria se reduce pero solo un 40-50% (gana 50 Mb, reduce 30-35 Mb), cada llamada aumenta la memoria en un rango de 10-15 Mb cada vez. El servicio está alojado dentro de Service Fabric.

Parece que tengo un problema similar en nuestro servicio de producción (20-100 req/s) usando una combinación de:

  • ASP.NET Core 2.0.4
  • ServiceStack.Core 1.0.44
  • SkiaSharp 1.59.2
  • Docker v17.09.0-ce, compila afdb6d4 en Ubuntu 16.04 x64
  • servidores x4 @ 40 CPU, 128 GB de memoria
  • Servidor GC es verdadero
  • cada contenedor Docker está configurado para 12k (mhz) de recursos compartidos de CPU, 8 GB de RAM

La aplicación tiene un servidor web front-end y un trabajador (que se muestran respectivamente en los gráficos a continuación).

servidor web (últimas 6h)
screen shot 2018-01-13 at 1 06 24 pm

trabajador (últimas 6h)
screen shot 2018-01-13 at 1 07 55 pm

Ambos hacen uso de matrices de bytes grandes porque el servicio actúa como un proxy de almacenamiento de objetos y, en consecuencia, coloca objetos en el LOH. Mi pregunta es, ¿es esta una limitación conocida de .NET Core en este momento? Parece que el LOH nunca se limpió por completo ni se fragmentó.

Habiendo dicho eso, SOH parece estar funcionando bien, ya que se limpian los objetos típicos de API web. ¿Alguna sugerencia? ¿Hay algún problema con mi configuración? He analizado el código y no puedo encontrar ninguna fuga de memoria evidente, y no estoy usando nada especial fuera de la biblioteca ServiceStack.

@sebastienros : ¿alguna idea sobre esto? ¿Hemos observado algún comportamiento similar en nuestros sistemas?

  • Solo medimos esto en 2.1, buscaré agregar lo mismo para 2.0
  • Todos los comentarios parecen estar relacionados con el problema de LOH que se mencionó, que debe tenerse en cuenta en la aplicación y agrupar arreglos grandes tanto como sea posible.

@sebastienros , varias preguntas:

  1. Utilicé Ants profiler para medir el uso de memoria, según él, no se detectó fragmentación de LOH. ¿Puede aconsejarme cómo puedo verificar si mi aplicación sufre problemas de fragmentación de LOH?
  2. ¿Cuáles son los resultados en .net core 2.1? ¿Se resolvió el problema porque Kestrel está usando Span??
  3. ¿Qué pasa si no podemos agrupar matrices? ¿Puede proporcionar una solución alternativa? ¿Deberíamos usar GCSettings.LargeObjectHeapCompactionMode.CompactOnce?

¿Cuáles son los resultados en .net core 2.1? ¿Se resolvió el problema porque Kestrel está usando Span?

Personalmente, no hemos visto ninguna evidencia de que el problema esté en Kestrel. Todavía parece un problema de aplicación.

¿Qué pasa si no podemos agrupar matrices? ¿Puede proporcionar una solución alternativa? ¿Deberíamos usar GCSettings.LargeObjectHeapCompactionMode.CompactOnce?

@jkotas @Maoni0 ¿ Algún consejo aquí?

¿Cómo puedo investigar si sufro el mismo problema? el LOH según el generador de perfiles de memoria redgate está casi vacío como lo describe @sinapis , pero aún usa más de 1 gb para un solo usuario

Recopile el rastro y analícelo usando perfview. Hay varios tutoriales de Vance y otros sobre cómo rastrear las fugas de memoria de .NET: https://www.bing.com/search?q=.NET%20memory%20leak%20perfview .

https://github.com/dotnet/coreclr/blob/master/Documentation/project-docs/linux-performance-tracing.md tiene instrucciones específicas de Linux para recopilar seguimientos.

Si cree que no hay pérdida de memoria y que GC solo conserva más memoria de la que le gustaría, puede intentar:

  • Usar los límites de memoria del objeto de trabajo de Windows o del contenedor de la ventana acoplable para limitar la cantidad de memoria que puede usar el proceso. El GC tiene en cuenta estos límites y funciona de forma más agresiva cuando está cerca de ellos.
  • O cambiando de Server GC a Workstation GC. No es inusual que el GC del servidor tenga un conjunto de trabajo máximo mucho más alto. La estación de trabajo tiene un conjunto de trabajo máximo más bajo, pero también un rendimiento más bajo.

Hola,

Creo que tengo el mismo tipo de problema con una API web .Net Core en producción.

La aplicación se ejecuta en Windows Server 2016 con .Net Core 2.0.3. La máquina es una máquina virtual Hyper-V con 28 núcleos de CPU y 24 GB de RAM. Si no reciclamos el grupo de aplicaciones de IIS con la suficiente frecuencia, eventualmente usaremos toda la memoria disponible. Cuando la aplicación comienza a utilizar mucha memoria (>=95 % de la memoria total del sistema), el uso de la CPU también aumenta considerablemente (a veces, del 2 % al 70 %). No estoy seguro de si se activa o no una excepción OOM, siempre reciclamos el grupo antes de que suceda (el uso máximo de memoria que he visto fue el 98% de la memoria utilizada por dotnet.exe).

Al analizar un volcado de memoria de producción con ".Net Memory Porfiler" (software SciTech), esto es lo que encontré:
image

Si este análisis es correcto, alrededor del 95 % de la memoria está en "sobrecarga > sin usar". Así es como este editor de perfiles de memoria describe esta categoría (en su foro):
_"Overhead->Unused" es la memoria confirmada por el tiempo de ejecución de .NET para el montón administrado. Actualmente no se utiliza, pero está disponible para futuras asignaciones de instancias. Hay muchas reglas que usa el tiempo de ejecución para decidir si mantener la memoria comprometida o liberarla al sistema operativo. Depende de factores como la memoria disponible, los patrones de asignación, la cantidad de procesadores, si se usa el servidor GC, etc._

@jkotas Aplicaré sus recomendaciones (objeto de trabajo de Windows y cambio a la estación de trabajo GC) y le informaré el resultado. Avíseme si puedo extraer cualquier otra información útil de los volcados de memoria de producción que tengo.

Gracias

@sinapis @ronald7
¿Alguno de ustedes podría compartir una aplicación que muestre el problema? Si pudiera reproducirlo, podríamos encontrar el motivo, o al menos eliminar parte del código pieza por pieza y aislar una reproducción mínima.

@sebastienros No puedo compartir la aplicación, pero puedo compartir la sesión desde PerfView session + memory dump .
Alguna descripción: tengo una API web ASP.NET Core 2, he creado una prueba de carga de 200 usuarios que envían la misma solicitud durante 10 segundos. En total, se procesaron 775 solicitudes.

Esta aplicación saltó a casi 1 GB de uso de memoria en el administrador de tareas y permaneció así. Mirando el volcado puedo contar unos 18 MB:

image

Entonces, la pregunta es ¿a dónde se fue casi 1 GB?

@sinapis Gracias

El comportamiento que está describiendo no es inesperado, el GC asignará algo de memoria según sea necesario en la carga máxima y simplemente la liberará con el tiempo. Es el modo de servidor GC y, por lo general, espera períodos de inactividad para liberarlo y no afectar el rendimiento de su aplicación. La cantidad de memoria que reservará depende de la memoria total disponible en el sistema.

Definitivamente veríamos un problema si siguiera aumentando. Supongo que si no envía más solicitudes y deja que su aplicación se ejecute, verá que el uso de la memoria disminuye.

¿Podría ejecutar lo mismo hasta que consuma la mayor parte de la memoria del sistema? ¿O al menos el tiempo suficiente con la misma carga que mostrará que crece continuamente? Seguiré echando un vistazo a tus vertederos actuales.

También puede realizar vertederos durante y al final de los trabajos, para que podamos ver los detalles.

Hola @sebastienros

Desafortunadamente, no puedo compartir la aplicación ni los volcados de memoria, pero crearé una aplicación ficticia (con la misma arquitectura y dependencias), la ejecutaré en la misma máquina, si puedo reproducir este comportamiento, compartiré esta con ustedes. Avíseme si hay alguna información útil que pueda extraer para usted de los volcados de memoria.

Actualicé el modo GC de _servidor_ a _estación de trabajo_ en un servidor de producción. Le avisaré dentro de unas horas si cambia algo en el uso de la memoria.

También realicé otra prueba: estamos ejecutando nuestra aplicación detrás de un balanceador de carga, en 4 máquinas virtuales. Después de eliminar una de las máquinas del grupo del equilibrador de carga, la memoria utilizada por dotnet.exe no disminuyó y permaneció en el mismo nivel incluso después de 30 minutos. (Sin embargo, la aplicación todavía estaba procesando algunas solicitudes: una solicitud enviada por SCOM en un extremo ficticio cada 30 segundos). No se liberó memoria ni se devolvió al sistema.

Gracias

@sinapis Miré tu rastro de ETW. Es desconcertante para mí: sobreviviste muy poco en el último GC gen2 inducido, pero aun así elegimos mantener esa cantidad de memoria comprometida. su aplicación parece un caso extremo (principalmente solo realizó GC en segundo plano debido a las asignaciones de LOH). Me pregunto si tenemos algunos errores contables allí (otra posibilidad son los errores en los números informados, pero si ya verificó que tiene tanto comprometido eso es un menor posibilidad). si pudieras reproducir algo que puedas compartir conmigo, sería fantástico; de lo contrario, si es posible ejecutar su aplicación con algún registro de GC (puedo darle un compromiso que lo haga), eso también sería útil.

@ Maoni0 por favor comparta cómo debo habilitar el registro de GC
Si hay otros datos que le gustaría que proporcione para refutar el error de contabilidad, hágamelo saber qué debo proporcionarle y cómo (¿tal vez decirle a perfview que recopile más datos?)
Intentaré crear una reproducción mínima, pero no estoy seguro de tener éxito ya que no sé dónde está el problema.

@sebastienros con suerte proporcionaré otro volcado con más consumo de memoria hoy

Hola @sebastienros @Maoni0 ,

Ejecuté nuestra aplicación con el modo GC de la estación de trabajo durante 12 horas pero con el mismo resultado. También volví a compilar la aplicación con .Net 2.1 Preview 2 en un solo nodo de producción durante 1 hora, les comunicaré el resultado, pero por ahora el proceso ya usa más de 2 GB de RAM.

image

Tengo PerfView ejecutándose en esta misma máquina y estoy recolectando volcados de GC, ¿hay alguna dirección de correo electrónico donde pueda enviarle el enlace de OneDrive? Lamentablemente, no puedo compartirlo directamente en este hilo.

Si puede ayudar, también puedo recopilar más métricas o registros de GC. Gracias

@ronald7 _redactado_ Puedo reenviar a @Maoni0

Hola @sebastienros @Maoni0 Acabo de enviarte un correo electrónico con dos PerfView gcdump y un archivo VMMap, espero que esto pueda ayudar. Por mi parte, todavía estoy tratando de reproducir este comportamiento de alto uso de memoria con una aplicación ficticia.

¡Gracias!

Yo también estoy experimentando el mismo problema. ¡La recolección de basura nunca sucede! La captura de pantalla muestra el uso de la memoria después de realizar unas 50 solicitudes con una aplicación de API web dotnet core bastante simple.

memory-profile2

Acabo de actualizar una aplicación ASP.NET Core que se ejecuta en Ubuntu 16.04 de 1.1 a 2.0 y encontré este problema. Es bastante grave, lo que hace que el núcleo elimine la aplicación con frecuencia debido a errores de OOM, y estoy contemplando la posibilidad de volver a la versión 1.x. Hay ciertas páginas que no podemos cargar en absoluto, incluso después de reiniciar Kestrel, ¡la aplicación agota inmediatamente la memoria disponible después de una sola solicitud! Pensé en actualizar el servidor, pero según los comentarios aquí sobre las aplicaciones ASP.NET Core que usan toda la memoria disponible, no espero que eso ayude. Nuestra pila es básicamente ASP.NET MVC Core + EF Core... nada demasiado sofisticado. Si tengo algo de tiempo, intentaré crear una muestra para reproducir el problema; no creo que deba ser tan difícil, dada la simplicidad de nuestra pila.

FWIW, el sistema que actualicé también tiene una aplicación de consola .NET Core, y no parece tener ningún problema de memoria después de la actualización 2.0, por lo que definitivamente parece ser un problema relacionado con ASP.NET Core.

@danports , ¿ha intentado llamar a GC.Collect() para ver si el uso de la memoria se reduce drásticamente? eso nos daría una pista por dónde debemos empezar. si GC.Collect() (o GC.Collect/GC.WaitingForPendingFinalizers/GC.Collect sequent) no puede hacer que el uso de la memoria disminuya drásticamente, simplemente significa que hay tanta memoria que necesita estar activa para que GC no pueda reclamarla.

@ Maoni0 Todavía no lo he probado. No creo que mi problema sea con GC, porque vi que el uso de la memoria disminuía de vez en cuando; solo parece que mis aplicaciones .NET Core 2.0 consumen aproximadamente 2-3 veces la memoria que consumían en comparación con cuando se ejecutaban en . NET Núcleo 1.1. 😞

Regresé a .NET Core 1.1 por ahora y lo revisaré más adelante cuando tenga más tiempo, probablemente después de que se lance .NET Core 2.1. (Me encontré con un montón de problemas con 2.0 y este fue solo uno de ellos).

GC.Collect() no ayuda. Probé una API web ASP.NET Core 2.0 y 2.1 muy simple que tiene un controlador que devuelve un diccionario de 200k enteros. La memoria asignada sigue aumentando con cada solicitud, aunque la aplicación no utilice más memoria.

@Serjster devolver 200K enteros (4B) tomaría 800KB. En este caso, se encuentra con el problema que se explica en este comentario: https://github.com/aspnet/Home/issues/1976#issuecomment -289336916

En este caso, debe usar un grupo de arreglos para reutilizarlos en todas las solicitudes.

También es bueno saber que si el código se ejecuta en modo de 64 bits, las matrices/listas, etc. que contienen punteros tienen el doble de tamaño en comparación con 32 bits. Si no recuerdo mal, el marco completo ejecuta cualquier código de CPU de 32 bits en un sistema operativo de 64 bits de forma predeterminada. Por lo tanto, las personas que migran su código pueden encontrar problemas de LOH accidentalmente.

Estoy trabajando con @Serjster , y esto es lo que he encontrado. Si creo un proyecto de api web estándar usando asp.net core (utilicé 2.1 en mi última prueba), noto que cuando ejecuto la herramienta de diagnóstico (o incluso verifico la memoria de trabajo del proceso configurada en el código), la cantidad de bytes regresa sigue subiendo cuando llego a un punto final. Por ejemplo, si tengo un solo punto final de API web que devuelve un Diccionariocon 20.000 artículos en él, sucede lo siguiente:

  1. La primera visita al método del controlador pone la memoria de proceso en 83 MB.
  2. Espero unos segundos y luego la segunda visita se mueve a 86 MB.
  3. Espero unos segundos y la tercera visita pasa a 90 MB.
  4. De nuevo - 94 MB.
  5. Hago esto n veces y finalmente alcanza los 304 MB. Una vez que hace esto, se nivela.

Si el objeto devuelto es un objeto de diferente tamaño, todos los números anteriores son simplemente más grandes o más pequeños (incluida la cantidad nivelada), pero el patrón de crecimiento es el mismo (también conocido como, crecerá y crecerá hasta que se estabilice después de muchas solicitudes) .

Si agrego GC.Collect en la llamada al método (para que ocurra en cada solicitud individual), el nivel de es mucho más bajo, pero todavía hay un período de crecimiento hasta que se nivela.

El otro punto de detalle interesante es la cantidad de objetos y el tamaño del montón al hacer instantáneas prácticamente no cambia con cada visita. Pero el gráfico de la memoria de proceso sigue mostrando un número cada vez más alto (esto también es cierto si toma el proceso y extrae el valor establecido de la memoria de trabajo).

Estoy empezando a sospechar que el gráfico muestra la memoria asignada (y esta memoria crece en función de alguna lógica de pronóstico de uso/demanda del núcleo de asp.net), pero esto no es necesariamente memoria consumida/fugada. Sin embargo, no sé lo suficiente como para confirmarlo, así que me pregunto si alguien con más conocimientos podría intervenir.

EDITAR - comentario de @davidfowl : con respecto a su comentario sobre las cosas que se recopilan rara vez ... esto podría tener sentido. Pero, ¿cuánto tiempo suele tardar? Puedo pasar más de 30 segundos entre solicitudes, y el GC nunca parece volver a bajar ese número de memoria en el cuadro de diagnóstico. Estoy seguro de que soy ignorante en algo aquí, pero solo tengo curiosidad.

EDICIÓN 2: ahora que he leído el enlace SO que David publicó anteriormente con más detalle, estoy empezando a pensar que este es definitivamente el problema que estamos viendo. Si estamos ejecutando en un entorno con memoria limitada (que estamos en nuestro entorno de desarrollo donde estamos viendo esto porque estamos siendo baratos) tenemos problemas con esto.

Edición 3 - Una pregunta persistente. ¿Por qué la memoria del proceso aumenta constantemente, pero el tamaño del almacenamiento dinámico no aumenta si se trata de un problema de LOH? En realidad, puedo entender esto ahora. El montón es la memoria utilizada. La memoria asignada al procesador es la memoria utilizada más los bloques de memoria fragmentada que no se utilizan.

@RemyArmstro , ¿puedes cambiar Dictionary<int, int> a SortedDictionary<int, int> ? Es probable que el diccionario esté asignando memoria continua, incluso podría agregar algunos datos adicionales a cada entrada. La forma en que se implementa SortedDictionary hará muchas asignaciones pequeñas en lugar de una grande.

Editar: si está serializando en una cadena y no directamente en la secuencia de salida de respuesta, eso también podría causar asignaciones de LOH.

@ wanton7 Su respuesta no entiende el punto. El diccionario es solo la punta del iceburg. Podemos usar listas, arreglos, etc. etc. y todos hacen lo mismo. Sin embargo, como se señaló, si LOH está causando esto, como parece, ¿entonces este comportamiento probablemente esté bien? Excepto que esto podría tener algunos efectos secundarios preocupantes, como ¿qué sucede cuando te quedas sin memoria? ¿Se bloquea tu aplicación?

@Serjster ok, pensé que solo tenía casos pequeños en los que esto está sucediendo. Para mí, es muy inusual tener listas grandes, arreglos como este y enviar tantos datos en una llamada API si no es binario. Por lo general, cuando tiene algún tipo de API web y obtiene algunos datos de ella, usa la paginación. No debería enviar 10000 entradas al lado del cliente.
Pero si tiene muchos problemas como este y no hay forma de cambiar el funcionamiento de su API, entonces creo que debería crear sus propias implementaciones fragmentadas de listas y diccionarios. Si realmente usa matrices tan grandes, puede reemplazarlas con sus listas fragmentadas o intentar agruparlas cuando se inicia la aplicación.

Desearía que Microsoft creara implementaciones fragmentadas que todos pudieran usar en situaciones como esta.

@wanton7 una vez más, te estás perdiendo el punto. No importa el tamaño de la lista. Incluso un solo elemento o una pequeña lista hace que suceda este problema.

@Serjster tal vez solo estoy ciego, pero no veo ninguna publicación tuya en la que hayas dicho que enviar un solo elemento o una lista pequeña hará que esto suceda. ¿Lo borraste?

O de @RemyArmstro Habla de diccionarios de diferentes tamaños. Revisé corefx y Dictionary asignará una matriz o estos

private struct Entry
{
  public int hashCode;    // Lower 31 bits of hash code, -1 if unused
  public int next;        // Index of next entry, -1 if last
  public TKey key;           // Key of entry
  public TValue value;         // Value of entry
}

La asignación de 85000 bytes provocará la asignación de LOH, por lo que un diccionario con una capacidad de 5313 entradas de clave int y valor int provocará la asignación de LOH. La capacidad no es lo mismo que el número o las entradas, la capacidad parece expandirse por números primos, verifique el método de cambio de tamaño privado del diccionario privado. Cada estructura podría tener una asignación adicional más relleno de memoria, por lo que incluso las entradas más bajas podrían causar la asignación de LOH.

Detalles de implementación del diccionario Dictionary.cs

Editar: URL fija

@wanton7 Gracias. Creo que nos damos cuenta de cuál es el problema ahora. Es solo un fastidio que no haya una solución excelente y simple para esto. Básicamente se reduce a ser más consciente de ello y ajustar la forma en que escribe el código. La desventaja es que esta memoria no administrada comienza a sentirse un poco más administrada. :( Al final, es posible que solo tengamos unas pocas áreas que realmente infrinjan este límite de asignación, pero una de las áreas es bastante central para nuestra aplicación, por lo que la vemos mucho actualmente. Creo que solo debemos repensar esa pieza, monitorear e intentar descubrir cualquier otra área en la que notemos este avance. ¡Gracias de nuevo!

De hecho, tenemos una situación similar pronto y necesitamos crear una implementación fragmentada IList<T> . Voy a usar un tamaño para fragmentos que se puede cambiar de bits, así que puedo usar el cambio de bits y la máscara para la indexación.

Me gustaría saber cuál es más beneficioso para GC, ¿una porción más grande o una porción más pequeña? Desde tamaños entre 1KB y 64KB. Los fragmentos más pequeños significan más referencias para GC, pero supongo que un fragmento más grande podría ser peor para la compactación y la fragmentación.

su comprensión es exactamente correcta: iría con tamaños no demasiado grandes; probablemente intente 4k/8k.

@Maoni0 gracias!

Elegí 4 KB, para que no tengamos sorpresas desagradables si alguna vez ejecutamos nuestro código en Mono. Descubrí al leer http://www.mono-project.com/docs/advanced/garbage-collector/sgen/working-with-sgen/ que el umbral de LOH es de solo 8000 bytes en Mono.

¿Hay algún progreso para este problema?

Estamos observando el mismo problema en el que la memoria del proceso continúa creciendo a pesar de que el tamaño del almacenamiento dinámico permanece igual (o disminuye).

@sebastienros , ¿puedes echar otro vistazo a esto? ¿Tal vez podamos proporcionar algunas instrucciones detalladas para ayudar a las personas a investigar más sus escenarios?

Aquí hay algunos elementos a considerar con nuestra situación:

  • Solo devolvemos un objeto Dictionary<int, int> con 1000 valores enteros almacenados. Nada más que eso.
  • Convertimos nuestro proyecto de .NET Core 2.0 --> 2.1
  • Estamos viendo este problema en MS Visual Studio 2017

Codifique de la siguiente manera:

    public async Task<IActionResult> SampleAction()
    {
        var list = new Dictionary<int, int>();
        for (int n = 0; n < 1000; n++) list.Add(n, n);
        return Ok(list);
    }

Para reproducirse debe simular algún tipo de carga moderada. Simplemente hicimos clic rápidamente usando Postman y pudimos observar este comportamiento. Una vez que dejamos de simular la carga, observamos cómo disminuía el tamaño del almacenamiento dinámico, pero la memoria del proceso seguía siendo la misma (incluso cuando forzamos GC).

También veo esto en uno de mis proyectos, pero también puedo volver a crearlo en una nueva API de .net core dirigida a .Net Core 2.1 (SDK 2.1.302).

Adjunté el proyecto de API de muestra que creé con Visual Studio 15.8.0 Preview 4. Para mostrar el aumento de la memoria, hice que una aplicación de consola central de .net golpeara el punto final GET de valores predeterminados cada medio segundo para obtener las 2 cadenas. El uso de la memoria del proceso es lento, pero en un proyecto que devuelve más datos, esto puede crecer rápidamente.

screenshot
WebApplication1.zip

Encontré esta publicación en el intercambio de pila:

https://stackoverflow.com/questions/48301031/why-doesnt-garbage-collector-in-net-core-2-0-free-all-memory

¿Alguien ha perfilado sus aplicaciones en modo de lanzamiento para ver si este comportamiento está presente? Probaré esto hoy y veré si el problema persiste.

Editar : Intenté crear perfiles en el modo de lanzamiento y el problema aún persiste. Incluso fuerzo un GC para ver si eso tendrá algún efecto.

image

@chrisaliotta gracias por vincular a esa publicación, no estaba al tanto de ese comportamiento. De hecho, sería interesante ver si eso explica lo que la gente está viendo.

@Eilon @chrisaliotta Gracias, pero esto no está relacionado con esta discusión. .NET que no libera memoria en el modo de depuración es un comportamiento bien conocido y es por eso que solo medimos el consumo de memoria (y las posibles fugas) en el modo de liberación. Además, incluso en el modo de lanzamiento, verá que la memoria aumenta hasta cierto punto con el tiempo debido al modo Server GC. Así que este ejemplo no prueba nada por dos razones diferentes.

@sebastienros Entonces, ¿el comportamiento que @beef3333 y yo estamos observando es consistente con lo que se espera? Es decir, ¿donde los bytes privados permanecen elevados a pesar de la disminución del tamaño del almacenamiento dinámico? Si es así, me parece extraño que cada solicitud incremental continúe causando que los bytes privados crezcan a pesar de que haya espacio libre en el montón.

En modo de depuración sí. Por lo tanto, intente usar el modo de liberación y ejecute su estrés durante mucho tiempo. Si la memoria aumenta indefinidamente, hay una pérdida de memoria. Si la memoria se recicla (incluso si requiere una cantidad significativa de memoria), todo está bien en su caso.

Acabo de probarlo nuevamente usando el mismo proyecto que adjunté en el modo de lanzamiento y estoy viendo exactamente el mismo comportamiento.

Probaré tu aplicación entonces, gracias.

Ejecuté la aplicación provista por @beef3333 localmente durante 2 horas, con una tasa de 5K RPS, y la memoria es estable (variación de 1 MB en general, a 400 MB en una máquina con 32 GB). El GC se llama correctamente con regularidad. También inspeccioné varios volcados con el tiempo y las diversas instancias se crearon y recopilaron como se esperaba. Estoy usando 2.1.1.

@sebastienros Gracias por investigar esto. Creo que la conclusión de todo esto es que:

  • La administración de memoria se comportará de manera diferente para una aplicación web .NET Core en comparación con su aplicación de escritorio común y corriente.
  • Los desarrolladores deben centrarse en el consumo de memoria promedio como solicitudes de función por segundo (RPS) durante un período de tiempo.
  • El aumento de bytes privados puede no ser siempre indicativo de pérdidas de memoria.

Corríjame si me equivoco, pero parece que .NET Core aumentará la memoria asignada en función de las solicitudes promedio para garantizar el tiempo de respuesta más rápido. Si es cierto, ¿sería seguro suponer que es probable que no se libere esta memoria asignada hasta que se reinicie el grupo de aplicaciones, o se liberará esta memoria asignada con el tiempo si el RPS disminuye?

Mismo problema.
Tengo un servicio back-end asp.net webapi.
Las pilas son asp.net mvc, autofac, automapper, castle.dynamicproxy, entidad framework core.
Toda la memoria se consumirá y luego el servicio fallará.

La versión es 2.1.0

@atpyk Actualizar a 2.1.1. Si eso no ayuda, realmente debería perfilar qué está guardando esa memoria. He usado https://www.jetbrains.com/dotmemory/ pero probablemente haya otras herramientas que también puedan hacer esto. Puede mostrar lo que realmente se asigna en el montón de objetos grandes (LOH).

¿Estás ejecutando en modo de 32 bits? Debido a que las asignaciones de almacenamiento dinámico de objetos grandes (más de ~85000 bytes) pueden causar excepciones de falta de memoria en el modo de 32 bits debido a la fragmentación. Puede superar este límite muy fácilmente usando Dictionary. Consulte este comentario https://github.com/aspnet/Home/issues/1976#issuecomment -393833505

Si está ejecutando su código en .Net Framework completo, el comportamiento predeterminado es ejecutar cualquier código de CPU en modo de 32 bits. Debe desmarcar Preferir 32 bits de la configuración del proyecto o establecer alguna configuración de registro en el registro de su servidor de forma predeterminada en 64 bits.

@wanton7 Muchas gracias. Probaré tus soluciones.

Actualicé a 2.1.2 e implementé en la aplicación web de Microsoft Azure con win-x64, pero no tuve efecto. @wanton7
dotnetcorememory

@atpyk , cree una instantánea de la memoria (dump_ y analícela (Visual Studio, MemoScope) para ver qué objeto está tomando toda la memoria, o simplemente aumentando la cantidad. También puede tomar dos y compararlos con el tiempo.

@sabastienros Creo que suficientes personas han expresado su preocupación sobre esto y que usted/MS debería comenzar a analizarlo usted mismo. Tal vez esto esté funcionando como pretendías, pero entonces el diseño es defectuoso.

Nuestras aplicaciones finalmente se quedan sin memoria y fallan, y todo esto se ejecuta en un entorno de producción de Azure. Esto no es aceptable.

@atpyk entonces suena como una pérdida de memoria. Perfila tu memoria para ver qué mantiene esa memoria como dijo @sebastienros .

Una pregunta, ¿estás usando ASP.NET Core? Leí tu primer comentario nuevamente y mencionas ASP.NET MVC. ASP.NET MVC y ASP.NET Core son dos productos completamente diferentes. Estos problemas y este repositorio para ASP..NET Core.

Editar: solo por los números de versión parece que está usando ASP.NET Core, pero quería asegurarse.

Usamos .net core MVC. @wanton7
Estoy haciendo analizar la memoria. Tal vez Castle Dynamic Proxy provoque una fuga de memoria.
memroy1
memroy2
memroy3
dynamicproxy

@Serjster , ¿sus programas se ejecutan en .NET Core de 32 bits y fallan con excepciones de falta de memoria? Si su código hace muchas asignaciones de LOH, entonces la fragmentación de la memoria es probablemente la razón.
Si está ejecutando en un entorno de 32 bits, tiene dos opciones, corrija su código para evitar LOH o cambie a un entorno de 64 bits.

No estoy muy familiarizado con Azure, pero después de buscar un poco en Google encontré esto https://blogs.msdn.microsoft.com/webdev/2018/01/09/64-bit-asp-net-core-on-azure-app -Servicio/

@Serjster Creo que no todos los informes aquí son iguales y prefiero verificar la validez de cada caso diferente. Algo como "mi aplicación tiene una pérdida de memoria" no significa que se deba al marco, por lo que prefiero asegurarme de que cada caso sea real.

Tomando su caso por ejemplo, "Creo" que respondí la razón por la cual su memoria está aumentando. ¿Has podido solucionarlo después de mi explicación? ¿O no resolvió el problema y, en este caso, puede proporcionar una aplicación que pueda ejecutar localmente para reproducir el problema?

@sebastienros Lo que terminamos encontrando fue que la asignación de memoria siguió aumentando, aunque el consumo de memoria no lo hizo. Sé que el núcleo de ASP.NET está haciendo algunas heurísticas para indicarle que debe captar más, pero parecía estar asignando constantemente más y más en cada solicitud. Casi me parece codicioso en este sentido, pero podría estar equivocado.

De cualquier manera, creo que el punto de @Serjster es que este hilo sigue creciendo porque claramente hay cierta confusión aquí. En la antigua tierra de ASP.NET (antes del núcleo 1, creo), no necesitábamos ver este comportamiento (al menos no en esta magnitud. Puede que no sea realmente un error/problema, pero definitivamente es algo que causa que mucha gente plantear la misma pregunta una y otra vez.

Sería bueno si hubiera un artículo oficial que realmente abordara este hilo de arriba a abajo, en lugar de ir en círculos como ha sido. Espero que eso ayude a aclarar.

Sería bueno si hubiera un artículo oficial que realmente abordara este hilo de arriba a abajo, en lugar de ir en círculos como ha sido. Espero que eso ayude a aclarar.

Secundo que. Sería bueno que supiéramos por qué .NET Core 2.1 es más "oportunista" en lo que respecta a la administración de memoria que las versiones anteriores.

@sebastienros , ¿podríamos tener un resumen de los problemas aquí? hay 81 comentarios; tengo la impresión de que no se tratan solo de los problemas (aunque no los he leído detenidamente, por lo que podría estar equivocado). si es así, ¿podemos enumerar todos los problemas distintos aquí y ver si tenemos repros para cada uno de ellos? hay suficientes personas que han mencionado el aumento de la memoria, creo que nos justifica obtener reproducciones y averiguar si se trata de problemas generales o no.

Por supuesto. Actualmente estoy tratando de identificar todos estos hilos y ver cuál es el estado de cada uno de ellos. Eventualmente cerraré este problema y reabriré otros más específicos que se centren en cada informe, porque este único hilo ya no es sostenible. Los vincularé aquí para aquellos en este hilo que quieran seguirlos.

Paralelamente, trabajaré en un documento que enumera todas las recomendaciones, los problemas de memoria conocidos (LOB, HttpClient, ...) y las formas de analizar e informar estos problemas.

Solo para asegurarle que nos preocupamos por este problema y para detectar pérdidas de memoria de manera preventiva, durante los últimos 6 meses hemos estado ejecutando aplicaciones ASP.NET de forma continua en Azure, tanto en Linux como en Windows, durante 24 horas y 7 días. Estas pruebas obtienen la última fuente de ASP.NET en cada iteración (diaria o semanal). Medimos RPS, latencia, CPU y uso de memoria. La aplicación utiliza EF Core, MVC y Razor, y se sostiene al 50 % de la CPU para simular una carga significativa.

Puede ver los resultados aquí públicamente en este sitio (buscar el informe diario): https://msit.powerbi.com/view?r=eyJrIjoiYTZjMTk3YjEtMzQ3Yi00NTI5LTg5ZDItNmUyMGRlOTkwMGRlIiwidCI6IjcyZjk4OGJmLTg2ZjEtNDFhZi05MWFiLTJkN2NkMDExZGI0NyIsImMiOjV9&pageName=ReportSectioneeff188c61c9c6e3a798

Esto nos ha permitido solucionar algunos problemas del pasado y estar seguros de que no hay fugas fundamentales en el sistema en este momento. Pero no está ni cerca de usar todos los componentes que enviamos, y es posible que haya problemas que debamos identificar. Proporcionar vertederos y aplicaciones que reproduzcan los problemas son las principales formas en que puede ayudarnos.

@sebastienros Gracias por las actualizaciones. Sé que en nuestro caso el problema se trataba más de una nueva preocupación de "asignación de memoria codiciosa", que originalmente confundimos con una pérdida de memoria. Ni siquiera estoy seguro de que haya un problema ahora, podría ser que las nuevas heurísticas de optimización sean mucho más agresivas. No estoy seguro... pero creo que está en el camino correcto al evaluar realmente este hilo y presentar algunas explicaciones/resúmenes consolidados sobre lo que la gente está viendo/entendiendo mal. ¡Buena suerte!

Todos los informes individuales han sido aislados y tendrán seguimiento individual, y se cerrarán si ya están resueltos. Siéntase libre de suscribirse a estos para mantenerse al tanto.

Paralelamente, trabajaré en un documento que enumera todas las recomendaciones, los problemas de memoria conocidos (LOB, HttpClient, ...) y las formas de analizar e informar estos problemas.

Este es un gran +1 de mi parte. Siento que uno de los mayores problemas aquí es lo difícil que se 'siente' recopilar información, para luego tratar de ayudar a determinar _cuál_ es el problema. Tener algunos documentos excelentes que nos permitan seguir algunas instrucciones para (i) recopilar, (ii) intentar un autodiagnóstico y (iii) publicar nuestros vertederos/hallazgos de una manera que sea eficiente para el equipo de MS realmente puede ayudar a ambos. lados de la valla.

Si nosotros (los desarrolladores) podemos estar mejor capacitados para diagnosticar y/o proporcionar información, entonces todos ganamos.

Gracias de nuevo por escuchar @sebastienros - ¡Muy apreciado, amigo!

¿Qué piensas acerca de la situación en la imagen de abajo?

Ejecutamos 4 WebApps dentro del mismo Plan. Inicialmente era B1, se amplió a S2 y la memoria siguió creciendo hasta que configuré la hambrienta aplicación web csproj:

<ServerGarbageCollection>false</ServerGarbageCollection>

y también deshabilite Application Insights

  1. Creo que dado que la memoria se puede mantener bajo control con la configuración anterior, no hay pérdida de memoria. ¿Correcto?
  2. ¿Es normal el comportamiento presentado?

memory eaten up

La misma situación aquí que @alexiordan , teníamos una consola .net core 2.1, que ejecutaba algunos IHostedServices alojados en Kube, DESDE microsoft/ dotnet:2.1-runtime AS base. Queríamos habilitar HealthChecks, así que agregamos asp.net solo con el middleware de HealthChecks y cambiamos la imagen base a microsoft/ dotnet:2.1-aspnetcore-runtime. El resultado fue OOM. Hemos logrado estabilizar la asignación de memoria agregandofalso en csproj.

Nuestro análisis mostró que en una aplicación asp.net, el GC recopila con menos frecuencia, y también la Cola del Finalizador se recorre con menos frecuencia.

Además, si obligamos al GC a recopilar y recorrer la cola del finalizador agregando lo siguiente en nuestra canalización,

Sistema.GC.Collect();
System.GC.WaitForPendingFinalizers();
Sistema.GC.Collect();

la asignación de memoria se mantuvo estable.

¿Qué piensas acerca de la situación en la imagen de abajo?

Ejecutamos 4 WebApps dentro del mismo Plan. Inicialmente era B1, se amplió a S2 y la memoria siguió creciendo hasta que configuré la hambrienta aplicación web csproj:

<ServerGarbageCollection>false</ServerGarbageCollection>

y también deshabilite Application Insights

  1. Creo que dado que la memoria se puede mantener bajo control con la configuración anterior, no hay pérdida de memoria. ¿Correcto?
  2. ¿Es normal el comportamiento presentado?

memory eaten up

Hola @alexiordan

También estamos viendo un perfil de memoria muy similar cuando usamos AI (aplicación web en net core 2.1), ¿ha progresado más en la solución de esto? Obviamente, queremos mantener la IA en las aplicaciones.

Es extraño que el uso crezca en cada solicitud, pero establecer lo anterior en falso parece detenerlo. extraño porque uno pensaría que verdadero sería el valor que lo habilita, pero parece al revés...

Olvidé mencionar que poco después de anunciar mi intención de escribir un artículo sobre los problemas descritos en este hilo, lo hice. Puedes verlo aquí: https://github.com/sebastienros/memoryleak

Viene con una pequeña aplicación que representa los patrones en un gráfico, en vivo.

pero establecer lo anterior en falso parece detenerlo para mí. extraño porque uno pensaría que verdadero sería el valor que lo habilita, pero parece al revés...

La recolección de elementos no utilizados del cliente (optimizada para compartir la memoria con muchas aplicaciones y mantener la memoria libre) es más agresiva que la recolección de elementos no utilizados del servidor (optimizada para el rendimiento y la simultaneidad).

Configurando SGC en falso, mi api central de asp.net cayó de 150 mb a 48 mb, y no creció en cada solicitud después de eso. Entonces, en realidad, ¿es este el mejor escenario para la producción, por ahora?

@kgrosvenor en realidad, depende. Citando el excelente artículo de @sebastienros :

En un entorno de servidor web típico, el recurso de la CPU es más crítico que la memoria, por lo que es más adecuado utilizar el Server GC. Sin embargo, algunos escenarios de servidor pueden adaptarse mejor a un GC de estación de trabajo, por ejemplo, en una alta densidad que aloja varias aplicaciones web donde la memoria se convierte en un recurso escaso.

Gracias, eso es muy útil, lo tendré en cuenta: seguiré este hilo para obtener más actualizaciones, dicho esto, me encanta el núcleo de asp.net :)

También sufra esto con una aplicación de consola .net core 2.1. crecimiento constante de la memoria. han establecido los contenedores docker en un máximo bajo para que lo golpee y se reinicie, lo que funciona bien, pero es feo.

¿Alguna novedad por este lado? También tenemos el mismo comportamiento en ASP.Net Core v2.1. Por lo que puedo ver en la confirmación https://github.com/aspnet/AspNetCore/commit/659fa967a1900653f7a82f02624c7c7995a3b786 , parece que hubo un problema de administración de memoria que se solucionará en v3.0.

@ flo8 , ¿ha intentado actualizar a 2.2 y ha comprobado el comportamiento? https://dotnet.microsoft.com/download

Ejecutar la última versión 2.2 y también tener este problema: simplemente crear una lista de 1 millón de entradas y devolver un resultado vacío () aumentará mi montón unos cientos de MB en cada solicitud hasta que me quede sin memoria. Establecer ServerGarbageCollection en false lo curó, aunque no parece ser la solución correcta...

@ dre99gsx parece que obtuvo una reproducción fácil, ¿podría compartir un proyecto y los pasos para que pueda hacer lo mismo localmente?

En este caso, debe llenar el LOB pero deben recopilarse en gen2. También comparta en qué entorno puedo reproducir esto, sistema operativo, memoria, carga.

Lo siento por la respuesta críptica. Bastante fácil:
(Windows 7 de 64 bits, 16 GB de RAM, Google Chrome para solicitudes http, comunidad VS2017) - Todavía me estoy acostumbrando a agregar código a estos hilos, perdón por la apariencia)

  • iniciar una nueva aplicación web .NET Core 2.2
  • Inyecte una clase de servicio, con alcance (nada especial ...) en el constructor del controlador
  • Haga que la acción Index() del controlador llame a un método de esta clase de servicio
  • Cree una clase modelo (DumbClass) con una propiedad: public int ID {get; colocar;}
  • En el método de clase de servicio, cree una instancia de una lista y complétela:
    var lst = nueva lista();
    for (i=0; i<10000000; i++) <--- nota: 10 millones de iteraciones
    {
    lst.add(nueva clase tonta(){ID=i});
    }
  • regrese del método, no necesita devolver nada, pero también puede devolver esa lista ...
  • index () devuelve un nuevo resultado vacío ();

Ahora solo llame a la acción cada 8 segundos y observe cómo aumenta la memoria del generador de perfiles. En mi sistema, esto es lo que veo en Bytes privados:

Iniciar aplicación: 155 MB
1ra solicitud de obtención de http: 809 MB
2do: 1.2GB
3ro: 1.4GB
4to: 1.8GB
5º: 2,1 GB... 2,3 GB... 2,6 GB...

Ahora, en algún momento, GC parece activarse, pero nunca cae por debajo de 3 GB en este ejemplo. Como se mencionó, si convierto el ServerGC en falso, nunca sube por encima de 1 GB, aunque todavía sube y se mantiene en 1 GB.

Avíseme si eso ayuda y si puede reproducirlo. Oh, leí su publicación de github: "Administración de memoria y patrones en ASP.NET Core", excelente redacción, ¡gracias por contribuir!

@dre99gsx 👋

Todavía me estoy acostumbrando a agregar código a estos hilos, perdón por la apariencia)

No hay ningún problema :) Pregunta: ¿podría poner toda la aplicación de muestra en GitHub (repositorios gratuitos) o en algún lugar similar? Esa es la forma más sencilla para que cualquier otra persona pueda clonar/descargar rápidamente _toda_la aplicación/repositorio de muestra exacto, con el que ha estado jugando.

Además, las capturas de pantalla del uso de la memoria usando TaskManager ayudarían (si está en Windows, o el equivalente en *nix .. que es el comando top ??)

¡Gran esfuerzo hasta ahora!

/me vuelve a mirar en silencio este hilo con seriedad.

Un recordatorio, puede ver algunas demostraciones y explicaciones de todos los síntomas que se describen en el hilo aquí: https://github.com/sebastienros/memoryleak

Además, cada uno de estos problemas se ha tratado de forma individual y ninguno ha demostrado ser un error en dotnet core __hasta ahora__, sino el comportamiento esperado.

@ dre99gsx Ahora, volviendo al comentario reciente, le insto a que presente un problema separado de este hilo. Estando en mi teléfono no me di cuenta que era "este" ;). Desde tu primer comentario afirmas

hasta que me quede sin memoria

Así que esperaría una excepción OutOfMemory, razón por la cual pedí una reproducción. Pero en tu próximo comentario dices:

Ahora, en algún momento, GC parece activarse, pero nunca cae por debajo de 3 GB en este ejemplo

Así que no hay problema de memoria. Esto es típico de una máquina con muchos núcleos y mucha memoria disponible. El GC liberará los montones administrados, pero la memoria seguirá estando comprometida ya que no hay razón para anular el compromiso (mucha memoria disponible). Este es un comportamiento estándar en .NET, y lo demuestro en el artículo que mencioné. También puede leer esto: https://blogs.msdn.microsoft.com/maoni/2018/11/16/running-with-server-gc-in-a-small-container-scenario-part-0/

Sé que el equipo de tiempo de ejecución está trabajando actualmente en formas de restringir las aplicaciones .NET que se ejecutan en contenedores para que no use tanta memoria, apuntando a 3.0, para resolver algunos escenarios de microservicios.

Y como usted mismo descubrió, si su aplicación no puede usar toda la memoria disponible en el servidor, debe usar el modo GC de la estación de trabajo.

Correcto, cuando dije "me quedé sin memoria", me baso en que visualmente no veo memoria disponible para ninguna otra aplicación a través de Windows Private Working Set (Administrador de tareas); no es una "excepción de falta de memoria".

Sabes qué, tienes razón. Esto se parece cada vez más al comportamiento esperado, y si hay tanta memoria disponible, ¿por qué gastar recursos en liberarla si nadie más la necesita? Lo entiendo. Mientras GC sea lo suficientemente inteligente como para liberar memoria para que otras aplicaciones no estén limitadas , lo mantendré como está y dejaré que haga lo suyo. ¡Gracias de nuevo!

Desarrollé mi aplicación en Asp.net core 2.2 y también enfrenté el mismo problema relacionado con la liberación de memoria.
cada llamada aumenta la memoria en un rango de 40-50 Mb cada vez que nunca se libera.

También he agregado la etiqueta mencionada ServerGarbageCollection>false
Todavía enfrenta el mismo problema para 50 usuarios, está utilizando aproximadamente 2 GB de RAM en modo En proceso (w3wp iis proceso de trabajo)

Por favor ayuda.

Por favor ayuda !! el mismo problema que ankitmori14

@ankitmori14 , @ikourfaln : si leyó el comentario de @sebastienros en https://github.com/aspnet/AspNetCore/issues/1976#issuecomment -449675298 y aún cree que hay un problema de memoria, envíe un nuevo problema con pasos detallados para reproducir el problema, además de cualquier otra información y rastro que tenga sobre el comportamiento. Recuerde que, a menos que la aplicación o el proceso tengan errores, es poco probable (pero no imposible) que haya un error. El recolector de basura 'Servidor' predeterminado no intenta usar la menor cantidad de memoria posible; más bien se activa cuando es necesario , como cuando la memoria se está agotando. Entonces, incluso una aplicación pequeña puede usar 2 GB de memoria y eso no es un problema porque todavía hay 4 GB libres.

Hola,

Estamos experimentando este problema: https://stackoverflow.com/questions/53868561/dotnet-core-2-1-hoarding-memory-in-linux

Básicamente, la memoria crece a lo largo de los días del proceso hasta que Kubernetes la elimina porque alcanza el límite configurado de 512 Mb. Curiosamente, la memoria se redujo drásticamente mientras realizaba un volcado de memoria, sin que el proceso se reiniciara ni nada. Examinando el volcado de memoria, vimos muchos objetos sin raíz.

También deshabilitamos el GC concurrente (es decir, el GC en segundo plano) ayer y parece estar mejor ahora, pero tendremos que esperar al menos una semana para confirmarlo.

<PropertyGroup>
  <ServerGarbageCollection>false</ServerGarbageCollection>
  <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection>
</PropertyGroup>

Pregunta de @vtortola , cuando configuró el límite de 512 Mb para su aplicación, ¿tenía una idea de la cantidad de solicitudes simultáneas que quería manejar o probó cuántas solicitudes simultáneas podría manejar su aplicación antes de fallar?

Hicimos algunas pruebas preliminares y aproximadas y verificamos que podíamos manejar 500 websockets simultáneos por pod usando 512 Mb. Corrimos durante horas con 2 pods y 1000 conexiones simultáneas con una memoria de menos de 150 Mb. La aplicación desplegada, con 2 pods, tiene entre 150 y 300 conexiones concurrentes en todo momento, y la memoria varía desde menos de 100Mb los primeros días, hasta llegar a los 512Mb en unas 2 semanas. No parece haber correlación entre el número de conexiones y la memoria utilizada. Más del 70% de las conexiones duran 10 minutos.

¿Podría compartir un volcado de memoria cuando esté en 100 MB y 512 MB para ver qué instancias siguen vivas?

Me temo que no puedo compartir un volcado, ya que contiene cookies, tokens y muchos datos privados.

¿Puedes comparar entonces localmente entonces? En términos de qué objeto ocupa la mayor parte de la memoria y si su número no se correlaciona con su carga. Por ejemplo, si tiene 300 conexiones, no debería haber objetos de conexión de 3K.

Desafortunadamente, la prueba de configuración de <ConcurrentGarbageCollection>false</ConcurrentGarbageCollection> no ayudó y el proceso todavía acumula memoria.

Tenemos un volcado de linux del proceso, la única herramienta que tenemos para analizarlo es lldb y somos muy novatos en este tema.

Aquí algunos datos por si te suena:

(lldb) eeheap -gc
Number of GC Heaps: 1
generation 0 starts at 0x00007F8481C8D0B0
generation 1 starts at 0x00007F8481C7E820
generation 2 starts at 0x00007F852A1D7000
ephemeral segment allocation context: none
         segment             begin         allocated              size
00007F852A1D6000  00007F852A1D7000  00007F853A1D5E90  0xfffee90(268430992)
00007F84807D0000  00007F84807D1000  00007F8482278000  0x1aa7000(27947008)
Large object heap starts at 0x00007F853A1D7000
         segment             begin         allocated              size
00007F853A1D6000  00007F853A1D7000  00007F853A7C60F8  0x5ef0f8(6222072)
Total Size:              Size: 0x12094f88 (302600072) bytes.
------------------------------
GC Heap Size:            Size: 0x12094f88 (302600072) bytes.

Y el resultado de dumpheap -stat : https://pastebin.com/ERN7LZ0n

Puede encontrar cómo Grafana informaba el estado de la memoria en https://stackoverflow.com/questions/53868561/dotnet-core-2-1-hoarding-memory-in-linux

Tenemos otro grupo de servicios en dotnet core que funcionan perfectamente, aunque no usan websockets.

@vtortola , ¿le importaría crear un nuevo problema para que no bifurquemos este? Creo que el hecho de que use WebSockets lo hace único y necesitamos crear al menos una aplicación de muestra que se comporte como la suya y la estrese durante un largo período de tiempo. ¿Sería capaz de describir qué hacen sus clientes con el websocket, cómo se conectan a él y durante cuánto tiempo? Podría configurar una aplicación como esta y ejecutarla durante días, y realizar volcados de memoria si veo que la memoria crece.

También podría haber algún conflicto entre el GC y Kubernetes, de manera que el GC no sea advertido de que está cerca del límite del pod.

@richlander está trabajando en un problema similar. Rich, ¿este caso (lea solo los últimos 6 comentarios) podría estar relacionado con lo que está trabajando?

si hace alguna diferencia, tengo el mismo (o muy similar) problema. Tengo más de una docena de aplicaciones de consola .net core 2.1 y 2.2 que se ejecutan en la ventana acoplable: cada contenedor tiene un límite de memoria de 512 MB, también usa websockets (cliente), y las aplicaciones alcanzan el límite de memoria del contenedor cada ~ 3 días. A continuación, el contenedor se apaga y se reinicia.

Todo usa servicios autenticados, por lo que no puedo proporcionar lo que tengo, pero podría armar algo que use un sitio de prueba para reproducir el problema.

¿Qué parámetros utiliza para establecer los límites de memoria? ¿Solo --memory o también --memory-swap ?

Cuando lo pruebo, puedo ver que se tiene muy en cuenta, nunca superando el límite que establecí, incluso con un límite súper bajo como 16MiB, sin embargo, se intercambia en el disco como un loco. Y estoy probando con una aplicación que realiza consultas de base de datos (EF) y representación de vistas de Razor.

@sebastienros absolutamente, creé https://github.com/aspnet/AspNetCore/issues/6803

Déjeme saber si usted necesita más información.

Actualmente estoy experimentando problemas de memoria.

Después de una hora en producción, el proceso w3wp.exe utiliza toda la memoria (ejecutando .NET Core InProcess). Cambiar el GC a la estación de trabajo no funcionó para mí.

Después de analizar un volcado de memoria, encontré este problema similar, https://github.com/aspnet/AspNetCore/issues/6102.

Espero probarlo en producción más tarde hoy, después de actualizar a la última ejecución de .NET Core, actualmente 2.2.3. Te dejaré saber cómo va.

Estoy teniendo problemas similares con el uso de la memoria. Si establezco límites en mis contenedores de Linux, solo lo hace OOM más rápido. Incluso sucede usando las plantillas básicas en Visual Studio. Una cosa que encontré: Core 2.1 y 2.2 están afectados. Core 2.0 no lo es, pero es EOL :(

Véase más arriba

Sé que el equipo de tiempo de ejecución está trabajando actualmente en formas de restringir las aplicaciones .NET que se ejecutan en contenedores para que no use tanta memoria, apuntando a 3.0, para resolver algunos escenarios de microservicios.

Y como usted mismo descubrió, si su aplicación no puede usar toda la memoria disponible en el servidor, debe usar el modo GC de la estación de trabajo.

Lamentablemente, el modo de estación de trabajo no ayuda en absoluto; lamentablemente, no estoy realmente en condiciones de probar una de las vistas previas de 3.0 o similar.

@PrefabPanda asegúrese de que realmente se está ejecutando en el modo GC de la estación de trabajo comprobando que System.Runtime.GCSettings.IsServerGC es false .

Entonces esto podría ser una pérdida de memoria genuina. Abra un problema separado, quizás el mejor paso.

@ wanton7 : ​​gracias, lo verifiqué dos veces y definitivamente está configurado.

@DAllanCarr - lo haré

Estoy casi seguro de que no es una pérdida de memoria. Más bien, es posible que la configuración de Docker no se aplique correctamente y que el GC no se active antes de que se alcance el límite. Sé que 3.0 introduce correcciones en este sentido.

@richlander , ¿este problema parece algo que se sabe que no funciona en 2.2?

@PrefabPanda , ¿le importaría compartir la versión exacta de la ventana acoplable que está utilizando y el archivo de composición de la ventana acoplable? Estoy tratando de reproducir, pero entre las versiones docker y docker-compose me resulta difícil replicar este problema localmente.

@sebastienros, @richlander - Gracias por responderme . Realmente lo aprecio.

Mis versiones de Docker:

Comunidad de escritorio de Docker
Versión 2.0.0.2 (30215)

Motor: 18.09.1
Componer: 1.23.2

Ver adjunto todo el proyecto de prueba:
WebApplication1.zip

solicitudes de curl de prueba.zip

Por si acaso, mi Dockerfile:

DESDE mcr.microsoft.com/dotnet/core/aspnet:2.2 COMO base
WORKDIR /aplicación
EXPONER 80
EXPONER 443

DESDE mcr.microsoft.com/dotnet/core/sdk:2.2 COMO compilación
DIR.TRABAJO /src
COPIAR ["WebApplication1/WebApplication1.csproj", "WebApplication1/"]
EJECUTAR dotnet restore "WebApplication1/WebApplication1.csproj"
COPIAR . .
WORKDIR "/src/WebApplication1"
EJECUTAR dotnet build "WebApplication1.csproj" -c Release -o /app

DESDE compilar COMO publicar
EJECUTAR dotnet publicar "WebApplication1.csproj" -c Release -o /app

DESDE base COMO final
WORKDIR /aplicación
COPIAR --desde=publicar /aplicación.
PUNTO DE ENTRADA ["dotnet", "WebApplication1.dll"]

docker-compose.yml:

versión: '2.4'

servicios:
aplicación web1:
imagen: ${DOCKER_REGISTRY-}webapplication1
mem_reserva: 128m
límite_mem: 256m
límite_memswap: 256m
CPU: 1
construir:
contexto: .
dockerfile: WebApplication1/Dockerfile

docker-compose-override.yml:

versión: '2.4'

servicios:
aplicación web1:
ambiente:
- ASPNETCORE_ENVIRONMENT=Desarrollo
- ASPNETCORE_URLS=https://+:443;http://+:80
- ASPNETCORE_HTTPS_PORT=44329
- DOTNET_EJECUTANDO_EN_CONTENEDOR=verdadero
- DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=verdadero
- ASPNETCORE_preventHostingStartup=verdadero
puertos:
- "50996:80"
- "44329:443"
volúmenes:
- ${APPDATA}/ASP.NET/Https:/root/.aspnet/ https:ro
- ${APPDATA}/Microsoft/UserSecrets:/root/.microsoft/us ersecrets:ro

@sebastienros : incluso si hay una manera en que puedo poner una variable de entorno separada en el contenedor para que la mire el GC, eso funcionaría bien para mí.

Intenté llamarlo explícitamente, pero no ayuda; supongo que incluso cuando lo llamo en código, todavía ve el tamaño de memoria incorrecto del contenedor/máquina.

@PrefabPanda cuando dijo "He intentado llamarlo explícitamente", ¿podría explicar qué significa exactamente? GC ve el límite de memoria del contenedor correcto si tiene uno especificado, ya sea que se active de forma natural o inducida. si llamó a GC.Collect(), hará una colección de bloqueo completa; y si hace GC.Collect(2, GCCollectionMode.Default, true, true) hará un GC de compactación completa, cuando esto devuelva el montón tiene el tamaño más pequeño posible, independientemente del límite del contenedor o cualquier otra cosa.

Hola @Maoni0 : probé GC.Collect(2, GCCollectionMode.Default, true, true)

Acabo de ver otro comentario que dice que 256 MB es demasiado pequeño para 2.2, pero podría estar 'arreglado' en 3.0. Parece que tendré que experimentar un poco más...

si probó GC.Collect(2, GCCollectionMode.Default, true, true) y la memoria no se redujo como esperaba, significa que tiene una verdadera pérdida de memoria. No estoy seguro de cuántas herramientas hay disponibles en su entorno. ¿Puedes ejecutar comandos SOS? si es así, siempre puede mirar para ver qué queda en el montón después de su GC inducido

Gracias @Maoni0 . Tenga en cuenta que estoy usando una plantilla en Visual Studio. Literalmente, no hay ningún código propio en este proyecto de prueba que adjunté. Tengo otros comentarios para trabajar, me pondré en contacto. Muchas gracias.

@ Maoni0 : intenté establecer el límite superior, no hubo diferencia. Parece que tendré que probar la versión preliminar 3.0

Me gustaría bloquear los comentarios sobre este problema, ya que es muy antiguo y todos sus casos se trataron en números separados. Comenté este por error pensando que estaba comentando en https://github.com/aspnet/AspNetCore/issues/10200

Gracias

¿Fue útil esta página
0 / 5 - 0 calificaciones