java - how - Recolector de basura G1: Perm Gen se llena indefinidamente hasta que se realiza un GC completo
java garbage collector how it works (5)
¿Es este el comportamiento esperado con G1?
No me sorprende. El supuesto básico es que las cosas puestas en permgen casi nunca se convierten en basura. Entonces, usted esperaría que el permgen GC sería un "último recurso"; es decir, algo que la JVM solo haría si se forzara en un GC completo. (OK, este argumento no está ni cerca de ser una prueba ... pero es consistente con lo siguiente).
He visto mucha evidencia de que otros coleccionistas tienen el mismo comportamiento; p.ej
- recolección de basura permgen toma múltiples GC completa
- ¿Qué está pasando con java GC? PermGen espacio se está llenando?
Encontré otra publicación en la web de alguien que cuestionaba algo muy similar y decía que G1 debería realizar colecciones incrementales en Perm Gen, pero no hubo respuesta ...
Creo que encontré el mismo post. Pero la opinión de alguien de que debería ser posible no es realmente instructiva.
¿Hay algo que pueda mejorar / corregir en nuestros parámetros de inicio?
Lo dudo. Mi entendimiento es que esto es inherente a la estrategia permgen GC.
Le sugiero que rastree y arregle lo que está usando tanto permgen en primer lugar ... o cambie a Java 8 en el que ya no hay un montón de permgen: vea la eliminación de PermGen en JDK 8
Mientras que una fuga de permgen es una explicación posible, hay otras; p.ej
- uso excesivo de
String.intern()
, - código de aplicación que está haciendo mucha generación dinámica de clases; por ejemplo, utilizando
DynamicProxy
, - una base de código enorme ... aunque eso no causaría una pérdida de permgen como parece que estás observando.
Tenemos una aplicación bastante grande que se ejecuta en un servidor de aplicaciones JBoss 7. En el pasado, usábamos ParallelGC, pero nos daba problemas en algunos servidores donde el montón era grande (5 GB o más) y, por lo general, casi se llenaba, obteníamos pausas de GC muy largas con frecuencia.
Recientemente, hicimos mejoras en el uso de la memoria de nuestra aplicación y en algunos casos agregamos más RAM a algunos de los servidores donde se ejecuta la aplicación, pero también comenzamos a cambiar a G1 con la esperanza de hacer que estas pausas sean menos frecuentes y / o más cortas. Las cosas parecen haber mejorado, pero estamos viendo un comportamiento extraño que no sucedió antes (con ParallelGC): el Perm Gen parece llenarse bastante rápido y una vez que alcanza el valor máximo, se activa un GC completo, que generalmente causa una larga pausa. en los hilos de aplicación (en algunos casos, más de 1 minuto).
Hemos estado usando 512 MB de tamaño máximo de perm por algunos meses y durante nuestro análisis, el tamaño de perm normalmente dejaría de crecer alrededor de 390 MB con ParallelGC. Después de cambiar a G1, sin embargo, el comportamiento anterior comenzó a suceder. Intenté aumentar el tamaño máximo de permanente a 1 GB e incluso a 1,5 GB, pero aún así están sucediendo los GC completos (son menos frecuentes).
En este enlace puede ver algunas capturas de pantalla de la herramienta de creación de perfiles que estamos utilizando (YourKit Java Profiler). Observe cómo cuando se activa el GC total, Eden y la vieja generación tienen mucho espacio libre, pero el tamaño de Perm es máximo. El tamaño del Perm y el número de clases cargadas disminuyen drásticamente después del Full GC, pero comienzan a subir nuevamente y el ciclo se repite. El caché de código está bien, nunca supera los 38 MB (en este caso es de 35 MB).
Aquí hay un segmento del registro de GC:
2013-11-28T11: 15: 57.774-0300: 64445.415: [Full GC 2126M-> 670M (5120M), 23.6325510 secs] [Eden: 4096.0K (234.0M) -> 0.0B (256.0M) Sobrevivientes: 22.0M > 0.0B Montón: 2126.1M (5120.0M) -> 670.6M (5120.0M)] [Tiempos: usuario = 10.16 sys = 0.59, real = 23.64 segs]
Puede ver el registro completo here (desde el momento en que iniciamos el servidor, hasta unos minutos después del GC completo).
Aquí hay alguna información del entorno:
versión java "1.7.0_45"
Java (TM) SE Runtime Environment (compilación 1.7.0_45-b18)
VM de servidor de 64 bits de Java HotSpot (TM) (compilación 24.45-b08, modo mixto)
Opciones de inicio: -Xms5g -Xmx5g -Xss256k -XX:PermSize=1500M -XX:MaxPermSize=1500M -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintAdaptiveSizePolicy -Xloggc:gc.log
Asi que aqui están mis preguntas:
¿Es este el comportamiento esperado con G1? Encontré otra publicación en la web de alguien que cuestionaba algo muy similar y decía que G1 debería realizar colecciones incrementales en Perm Gen, pero no hubo respuesta ...
¿Hay algo que pueda mejorar / corregir en nuestros parámetros de inicio? El servidor tiene 8 GB de RAM, pero no parece que nos falte hardware, el rendimiento de la aplicación está bien hasta que se activa un GC completo, que es cuando los usuarios experimentan grandes retrasos y comienzan a quejarse.
Debería iniciar su server.bat con el comando java con -verbose: gc
Estoy de acuerdo con la respuesta anterior en que realmente deberías tratar de encontrar lo que realmente está llenando tu permgen, y sospecho que es sobre una fuga en el cargador de clases para la que deseas encontrar una causa raíz.
Hay un hilo en los foros de JBoss que analiza algunos de estos casos diagnosticados y cómo se solucionaron. Esta respuesta y este artículo abordan el tema en general también. En ese artículo hay una mención de posiblemente la prueba más fácil que puedes hacer:
Síntoma
Esto ocurrirá solo si vuelve a implementar su aplicación sin reiniciar el servidor de aplicaciones. La serie JBoss 4.0.x sufrió una fuga en el cargador de clases. Como resultado, no pude volver a implementar nuestra aplicación más de dos veces antes de que la JVM se quedara sin memoria PermGen y se bloquee.
Solución
Para identificar dicha fuga, desinstale la aplicación y luego active un volcado de pila completo (asegúrese de activar un GC antes de eso). A continuación, compruebe si puede encontrar alguno de sus objetos de aplicación en el volcado. Si es así, siga sus referencias a su raíz y encontrará la causa de la fuga de su cargador de clases. En el caso de JBoss 4.0, la única solución fue reiniciar para cada redistribución.
Esto es lo que intentaría primero, si cree que la redistribución podría estar relacionada. Esta publicación del blog es anterior, hace lo mismo pero también discute los detalles. Según la publicación, puede ser que no estés redistribuyendo nada, pero permgen solo se está llenando por sí mismo. En ese caso, el examen de clases + cualquier otra cosa agregada al permgen podría ser la forma (como ya se ha mencionado en la respuesta anterior).
Si eso no da más información, mi próximo paso sería probar la herramienta Plumbr . También tienen una especie de garantía para encontrar la filtración para usted .
Primero intentaría encontrar la causa raíz para que el PermGen se haga más grande antes de probar aleatoriamente las opciones de JVM.
- Puede habilitar el registro de carga de clases (-verbose: class, -XX: + TraceClassLoading -XX: + TraceClassUnloading, ...) y desactivar la salida
- En su entorno de prueba, puede intentar monitorear (a través de JMX) cuando las clases se cargan (java.lang: type = ClassLoading LoadedClassCount). Esto podría ayudarlo a descubrir qué parte de su aplicación es responsable.
- También puedes intentar enumerar todas las clases usando las herramientas JVM (lo siento, pero aún uso principalmente jrockit y allí lo harías con jrcmd. Espero que Oracle haya migrado esas características útiles a Hotspot ...)
En resumen, averigüe qué genera tantas clases y luego piense cómo reducir eso / sintonizar el gc.
Saludos, Dimo
Causas del crecimiento de Perm Gen
- Muchas clases, especialmente JSPs.
- Muchas variables estáticas.
- Hay una fuga en el cargador de clases.
Para aquellos que no lo saben, aquí hay una forma sencilla de pensar cómo se llena el PremGen. La generación joven no tiene tiempo suficiente para dejar que las cosas expiren, por lo que se trasladan al espacio de la vieja generación. Perm Gen contiene las clases para los objetos en Young y Old Gen. Cuando los objetos en Young o Old Gen se recolectan y la clase ya no está siendo referenciada, se "descarga" de Perm Gen. Si el Young y Old Gen no recibe GC, tampoco la Perm Gen y, una vez que se llena, necesita un GC completo para detener el mundo. Para más información ver Presentando la Generación Permanente .
Cambiando a CMS
Sé que está utilizando G1, pero si cambia al colector de pausa baja de barrido de marca concurrente (CMS) -XX:+UseConcMarkSweepGC
, intente habilitar la descarga de clases y las colecciones de generación permanente agregando -XX:+CMSClassUnloadingEnabled
.
El Gotcha Oculto
Si está utilizando JBoss, RMI / DGC tiene gcInterval establecido en 1 minuto. El subsistema RMI fuerza una recolección de basura completa una vez por minuto. Esto, a su vez, obliga a la promoción en lugar de dejar que se acumule en la Generación Joven.
Debe cambiarlo por lo menos a 1 hora, si no a 24 horas, para que el GC haga las recolecciones adecuadas.
-Dsun.rmi.dgc.client.gcInterval=3600000 -Dsun.rmi.dgc.server.gcInterval=3600000
Lista de todas las opciones de JVM
Para ver todas las opciones, ejecute esto desde la línea cmd.
java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal -version
Si desea ver qué está utilizando JBoss, debe agregar lo siguiente a su standalone.xml
. Obtendrá una lista de todas las opciones de JVM y su configuración. NOTA: debe estar en la JVM que desea ver para usarlo. Si lo ejecuta de forma externa, no verá lo que está sucediendo en la JVM en la que se está ejecutando JBoss.
set "JAVA_OPTS= -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal %JAVA_OPTS%"
Hay un atajo para usar cuando solo estamos interesados en las banderas modificadas.
-XX:+PrintcommandLineFlags
Diagnósticos
Usa jmap para determinar qué clases consumen espacio de generación permanente. La salida mostrará
- cargador de clases
- # de clases
- bytes
- cargador principal
- vivo muerto
- tipo
totales
jmap -permstat JBOSS_PID >& permstat.out
Esta configuración funcionó para mí, pero dependiendo de cómo esté configurado su sistema y qué está haciendo su aplicación, determinará si son adecuados para usted.
-XX:SurvivorRatio=8
- Establece la relación de espacio de sobreviviente en 1: 8, lo que resulta en espacios de sobreviviente más grandes (cuanto más pequeña es la relación, mayor es el espacio). El SurvivorRatio es el tamaño del espacio de Eden en comparación con un espacio de sobreviviente. Los espacios de sobrevivientes más grandes permiten que los objetos de vida corta mueran en la generación joven durante un período de tiempo más largo.-XX:TargetSurvivorRatio=90
: permite que el 90% de los espacios de sobrevivientes se ocupen en lugar del 50% predeterminado, lo que permite una mejor utilización de la memoria de espacio de sobrevivientes.-XX:MaxTenuringThreshold=31
- Para evitar la promoción prematura de la generación joven a la antigua. Permite a los objetos de vida corta un período de tiempo más largo para morir en la generación joven (y por lo tanto, evitar la promoción). Una consecuencia de este ajuste es que los tiempos de GC menores pueden aumentar debido a la copia de objetos adicionales. Es posible que este valor y el tamaño de los espacios de sobrevivientes deban ajustarse para equilibrar los gastos generales de copia entre los espacios de sobrevivientes y los objetos de tenencia que van a vivir durante mucho tiempo. La configuración predeterminada para CMS es SurvivorRatio = 1024 y MaxTenuringThreshold = 0, lo que hace que se promocione a todos los sobrevivientes de un hurto. Esto puede ejercer mucha presión sobre el único hilo concurrente que recopila la generación en pie. Nota: cuando se usa con -XX: + UseBiasedLocking, esta configuración debe ser 15.-XX:NewSize=768m
- permite la especificación de los tamaños iniciales de la generación joven-XX:MaxNewSize=768m
- permite la especificación de los tamaños máximos de generación joven
Aquí hay una lista más extensa de opciones de JVM .