yugioh recolector recoleccion qué garbagecollector forzar example español ejemplo definicion como collector basura java memory memory-management playframework

recoleccion - ¿Por qué java espera tanto tiempo para ejecutar el recolector de basura?



recoleccion de basura definicion (6)

Estoy construyendo una aplicación web Java, ¡utilizando Play! Marco . Lo estoy alojando en playapps.net . He estado desconcertando por un tiempo sobre los gráficos proporcionados del consumo de memoria. Aquí hay una muestra:

El gráfico proviene de un período de actividad constante pero nominal. No hice nada para desencadenar la caída en la memoria, así que supongo que ocurrió porque el recolector de basura se ejecutó ya que casi ha alcanzado su consumo de memoria permitido.

Mis preguntas:

  • ¿Es justo para mí suponer que mi aplicación no tiene una pérdida de memoria, ya que parece que el recolector de basura recupera correctamente toda la memoria cuando se ejecuta?
  • (del título) ¿Por qué Java espera hasta el último segundo posible para ejecutar el recolector de basura? Estoy viendo una degradación significativa del rendimiento a medida que el consumo de memoria crece al cuarto más alto del gráfico.
  • Si mis afirmaciones anteriores son correctas, ¿cómo puedo solucionar este problema? Las otras publicaciones que he leído en SO parecen opuestas a las llamadas a System.gc() , que van desde neutral ("es solo una solicitud para ejecutar GC, por lo que la JVM puede simplemente ignorarlo") hasta la oposición directa ("código que depende de System.gc() está fundamentalmente roto "). ¿O estoy fuera de la base aquí, y debería buscar defectos en mi propio código que esté causando este comportamiento y la pérdida de rendimiento intermitente?

ACTUALIZAR
Abrí una discusión sobre PlayApps.net que apunta a esta pregunta y menciono algunos de los puntos aquí; específicamente el comentario de @ Affe sobre la configuración de un GC completo que se establece de manera muy conservadora, y el comentario de @G_H sobre la configuración del tamaño de almacenamiento dinámico inicial y máximo.

Aquí hay un enlace a la discusión , aunque lamentablemente necesita una cuenta de playapps para verla.

Informaré los comentarios aquí cuando lo obtenga; muchas gracias a todos por sus respuestas, ¡ya aprendí mucho de ellos!

Resolución
El soporte de Playapps, que todavía es genial, no tenía muchas sugerencias para mí, con la única idea de que si estaba usando la memoria caché extensamente, podría mantener los objetos vivos más de lo necesario, pero ese no es el caso. Todavía aprendí muchísimo (woo hoo!), Y le di a @Ryan Amos el cheque verde cuando tomé su sugerencia de llamar a System.gc() cada medio día, lo que por ahora está funcionando bien.


Como habrás notado, esto no te afecta. La recolección de basura solo se activa si la JVM siente que es necesario que se ejecute y esto sucede por el bien de la optimización, no hay necesidad de hacer muchas colecciones pequeñas si puede hacer una sola colección completa y hacer una limpieza completa.

La JVM actual contiene algunos algoritmos realmente interesantes y la propia colección de basura está dividida en 3 regiones diferentes, aquí puedes encontrar mucho más sobre esto, aquí hay una muestra:

Tres tipos de algoritmos de colección

El HotSpot JVM proporciona tres algoritmos GC, cada uno ajustado para un tipo específico de colección dentro de una generación específica. La colección de copia (también conocida como eliminación) rápidamente limpia los objetos efímeros en el montón de nueva generación. El algoritmo mark-compact emplea una técnica más lenta y robusta para recolectar objetos de mayor duración en el montón de la vieja generación. El algoritmo incremental intenta mejorar la recopilación de la generación anterior mediante la realización de un GC robusto mientras se minimizan las pausas.

Copiar / recuperar colección

Usando el algoritmo de copia, la JVM recupera la mayoría de los objetos en el espacio de objetos de la nueva generación (también conocido como eden) simplemente haciendo pequeños barridos, un término de Java para recolectar y eliminar desperdicios. Los objetos de larga duración se copian o permanecen en el antiguo espacio de objetos.

Colección Mark-compact

A medida que más objetos se vuelven permanentes, el espacio de objetos antiguo comienza a alcanzar la ocupación máxima. El algoritmo mark-compact, utilizado para recoger objetos en el antiguo espacio de objetos, tiene diferentes requisitos que el algoritmo de recopilación de copias utilizado en el nuevo espacio de objetos.

El algoritmo mark-compact primero escanea todos los objetos, marcando todos los objetos alcanzables. Luego compacta todas las lagunas restantes de objetos muertos. El algoritmo mark-compact ocupa más tiempo que el algoritmo de recopilación de copias; sin embargo, requiere menos memoria y elimina la fragmentación de la memoria.

Colección incremental (tren)

La nueva generación copy / scavenge y los algoritmos de compases de marca de la antigua generación no pueden eliminar todas las pausas de JVM. Tales pausas son proporcionales a la cantidad de objetos en vivo. Para abordar la necesidad de GC sin pausa, la JVM de HotSpot también ofrece una colección incremental o de tren.

La recopilación incremental divide las pausas de recopilación de objetos antiguas en muchas pausas pequeñas incluso con áreas de objetos grandes. En lugar de solo una generación nueva y una antigua, este algoritmo tiene una generación intermedia que comprende muchos espacios pequeños. Hay una sobrecarga asociada con la recolección incremental; es posible que vea una degradación de la velocidad del 10 por ciento.

Los parámetros -Xincgc y -Xnoincgc controlan cómo usa la colección incremental. La próxima versión de HotSpot JVM, versión 1.4, intentará GC continuo sin pausa que probablemente será una variación del algoritmo incremental. No discutiré la recolección incremental ya que pronto cambiará.

Este colector de basura generacional es una de las soluciones más eficientes que tenemos para el problema hoy en día.


Cualquier respuesta detallada dependerá del recolector de basura que esté utilizando, pero hay algunas cosas que son básicamente las mismas en todos los GC (modernos, de sol / oráculo).

Cada vez que ve que el uso en el gráfico disminuye, eso es una recolección de basura. La única forma en que Heap se libera es a través de la recolección de basura. El caso es que hay dos tipos de colecciones de basura, menores y completas. El montón se divide en dos "áreas" básicas. Joven y tenured. (En realidad, hay muchos más subgrupos.) Cualquier cosa que ocupe espacio en Young y todavía esté en uso cuando aparezca el GC menor para liberar algo de memoria, será "promovido" a titular. Una vez que algo da el salto a la tenencia, se queda indefinidamente hasta que el montón no tenga espacio libre y se necesite una recolección completa de basura.

Entonces, una interpretación de ese gráfico es que su generación joven es bastante pequeña (por defecto puede ser un porcentaje bastante pequeño del total en algunas JVM) y mantiene los objetos "vivos" durante comparativamente tiempos muy largos. (¿Tal vez está reteniendo referencias a ellos en la sesión web?) Así que sus objetos son colecciones de basura ''supervivientes'' hasta que son promovidos a un espacio fijo, donde permanecen indefinidamente hasta que la JVM está bien y realmente bien fuera de la memoria.

Nuevamente, esa es solo una situación común que se ajusta a la información que tiene. Necesitaría detalles completos sobre la configuración de JVM y los registros de la GC para realmente asegurar lo que está sucediendo.


Java no ejecutará el limpiador de basura hasta que tenga que hacerlo, porque el limpiador de basura ralentiza un poco las cosas y no debería ejecutarse con tanta frecuencia. Creo que estarás bien para programar una limpieza con más frecuencia, como cada 3 horas. Si una aplicación nunca consume memoria completa, no debería haber ninguna razón para ejecutar el limpiador de basura, por lo que Java solo la ejecuta cuando la memoria es muy alta.

Entonces, básicamente, no te preocupes por lo que dicen los demás: haz lo que funcione mejor. Si encuentra mejoras en el rendimiento al ejecutar el limpiador de basura con un 66% de memoria, hágalo.


Me doy cuenta de que el gráfico no está inclinado estrictamente hacia arriba hasta la caída, sino que tiene variaciones locales más pequeñas. Aunque no estoy seguro, no creo que el uso de la memoria muestre estas pequeñas gotas si no hay recogida de basura.

Hay colecciones menores y mayores en Java. Las colecciones menores ocurren con frecuencia, mientras que las colecciones principales son más raras y disminuyen más el rendimiento. Las colecciones menores probablemente tienden a barrer cosas como instancias de objetos de vida corta creadas dentro de métodos. Una colección importante eliminará mucho más, que es lo que probablemente sucedió al final de su gráfico.

Ahora, algunas respuestas que se publicaron mientras escribo esto dan buenas explicaciones con respecto a las diferencias en recolectores de basura, generaciones de objetos y más. Pero eso aún no explica por qué tomaría un tiempo tan absurdamente largo (casi 24 horas) antes de que se realice una limpieza seria.

Dos cosas de interés que pueden establecerse para una JVM al inicio son el tamaño de almacenamiento dinámico máximo permitido y el tamaño de almacenamiento dinámico inicial. El máximo es un límite estricto, una vez que lo alcanza, una recolección de basura adicional no reduce el uso de memoria y si necesita asignar espacio nuevo para objetos u otros datos, obtendrá un OutOfMemoryError. Sin embargo, internamente también hay un límite suave: el tamaño del montón actual. Una JVM no devora inmediatamente la cantidad máxima de memoria. En cambio, comienza en su tamaño de almacenamiento dinámico inicial y luego aumenta el montón cuando es necesario. Piense en ello un poco como la RAM de su JVM, que puede aumentar dinámicamente.

Si el uso real de la memoria de su aplicación comienza a alcanzar el tamaño de pila actual, típicamente se instigará una recolección de basura. Esto podría reducir el uso de la memoria, por lo que no es necesario aumentar el tamaño del almacenamiento dinámico. Pero también es posible que la aplicación actualmente necesite toda esa memoria y exceda el tamaño del montón. En ese caso, se aumenta siempre que no haya alcanzado el límite máximo establecido.

Ahora, lo que podría ser su caso es que el tamaño del montón inicial se establece en el mismo valor que el máximo. Supongamos que así sea, entonces la JVM aprovechará inmediatamente todo ese recuerdo. Tomará mucho tiempo antes de que la aplicación acumule suficiente basura para alcanzar el tamaño de almacenamiento dinámico en el uso de la memoria. Pero en ese momento verás una gran colección. Comenzar con un montón lo suficientemente pequeño y permitir que crezca, mantiene el uso de la memoria limitado a lo que se necesita.

Esto supone que su gráfico muestra el uso del montón y el tamaño del montón no asignado. Si ese no es el caso y en realidad estás viendo crecer el montón en sí mismo, está sucediendo algo más. Admitiré que no soy lo suficientemente inteligente con respecto a los aspectos internos de la recolección de basura y su programación para estar absolutamente seguro de lo que está sucediendo aquí, la mayor parte de esto es a partir de la observación de aplicaciones filtradas en perfiladores. Entonces, si proporcioné información incorrecta, tomaré esta respuesta.


Probablemente tiene fugas de memoria que se borran cada 24 horas.


Tuve una aplicación que produjo un gráfico como ese y actuó como usted describe. Estaba usando el recopilador de CMS (-XX: + UseConcMarkSweepGC). Esto es lo que estaba pasando en mi caso.

No tenía suficiente memoria configurada para la aplicación, así que con el tiempo me encontré con problemas de fragmentación en el montón. Esto causó GC con mayor y mayor frecuencia, pero en realidad no arrojó un OOME o falló de CMS al colector serial (lo que se supone que debe hacer en ese caso) porque las estadísticas que mantiene solo cuentan el tiempo de pausa de la aplicación (bloques de GC) el mundo), el tiempo concurrente de la aplicación (GC se ejecuta con subprocesos de aplicación) se ignora para esos cálculos. Sintonicé algunos parámetros, principalmente le di mucha más carga (con un espacio nuevo muy grande), establecí -XX: CMSFullGCsBeforeCompaction = 1, y el problema se detuvo.