java - tipos - Reducir el tiempo de pausa de JVM> 1 segundo con UseConcMarkSweepGC
uso de la coma (8)
Estoy ejecutando una aplicación de memoria intensiva en una máquina con 16 Gb de RAM, y un procesador de 8 núcleos, y Java 1.6 todos ejecutan en CentOS versión 5.2 (Final). Los detalles exactos de JVM son:
java version "1.6.0_10"
Java(TM) SE Runtime Environment (build 1.6.0_10-b33)
Java HotSpot(TM) 64-Bit Server VM (build 11.0-b15, mixed mode)
Estoy lanzando la aplicación con las siguientes opciones de línea de comando:
java -XX:+UseConcMarkSweepGC -verbose:gc -server -Xmx10g -Xms10g ...
Mi aplicación expone una API JSON-RPC, y mi objetivo es responder a las solicitudes dentro de 25 ms. Desafortunadamente, veo retrasos que exceden 1 segundo y parece ser causado por la recolección de basura. Estos son algunos de los ejemplos más largos:
[GC 4592788K->4462162K(10468736K), 1.3606660 secs]
[GC 5881547K->5768559K(10468736K), 1.2559860 secs]
[GC 6045823K->5914115K(10468736K), 1.3250050 secs]
Cada uno de estos eventos de recolección de basura estuvo acompañado de una respuesta API diferida de duración muy similar a la duración de la recolección de basura mostrada (dentro de unos pocos ms).
Aquí hay algunos ejemplos típicos (todos fueron producidos en pocos segundos):
[GC 3373764K->3336654K(10468736K), 0.6677560 secs]
[GC 3472974K->3427592K(10468736K), 0.5059650 secs]
[GC 3563912K->3517273K(10468736K), 0.6844440 secs]
[GC 3622292K->3589011K(10468736K), 0.4528480 secs]
El caso es que pensé que UseConcMarkSweepGC evitaría esto, o al menos lo haría extremadamente raro. Por el contrario, los retrasos que superan los 100 ms se producen casi una vez por minuto o más (aunque los retrasos de más de 1 segundo son considerablemente más escasos, tal vez una vez cada 10 o 15 minutos).
La otra cosa es que pensé que solo un GC COMPLETO haría que los hilos se detuvieran, pero estos no parecen ser GC completos.
Puede ser relevante observar que la mayor parte de la memoria está ocupada por un caché de memoria LRU que hace uso de referencias suaves.
Cualquier asistencia o consejo sería muy apreciado.
Algunos lugares para empezar a buscar:
- https://visualvm.dev.java.net/
- http://java.sun.com/j2se/1.5.0/docs/tooldocs/share/jstat.html
- http://www.javaperformancetuning.com/tools/gcviewer/index.shtml
También corría el código a través de un generador de perfiles. Me gusta el de NetBeans, pero también hay otros. Puede ver el comportamiento de gc en tiempo real. La VM visual también hace eso ... pero todavía no lo he ejecutado (he estado buscando una razón para ... pero aún no he tenido el tiempo o la necesidad).
En primer lugar, consulte la documentación de ajuste de recolección de basura de la máquina virtual Java SE 6 HotSpot [tm] , si aún no lo ha hecho. Esta documentación dice:
el recopilador simultáneo realiza la mayor parte de su trabajo de rastreo y barrido con los subprocesos de la aplicación aún ejecutándose, de modo que las subprocesos de la aplicación solo ven breves pausas. Sin embargo, si el recopilador concurrente no puede finalizar la recuperación de los objetos inalcanzables antes de que se llene la generación titular, o si no se puede satisfacer una asignación con los bloques de espacio libre disponibles en la generación permanente, la aplicación se pausa y la recopilación se completa con todos los hilos de la aplicación detenidos. La incapacidad de completar una colección simultáneamente se conoce como falla de modo concurrente e indica la necesidad de ajustar los parámetros del recopilador concurrente.
y un poco más tarde ...
El recopilador simultáneo pausa una aplicación dos veces durante un ciclo de recopilación simultáneo.
Observo que esos GC no parecen liberar mucha memoria. Tal vez muchos de sus objetos son de larga vida? Es posible que desee ajustar los tamaños de generación y otros parámetros de GC. 10 Gig es un gran montón para muchos estándares, e ingenuamente esperaría que GC llevara más tiempo con un montón tan grande. Aún así, 1 segundo es un tiempo de pausa muy largo e indica que algo está mal (su programa está generando una gran cantidad de objetos innecesarios o está generando objetos difíciles de reclamar, o algo más) o simplemente necesita sintonizar el GC.
Por lo general, le digo a alguien que si tienen que sintonizar GC, tienen otros problemas que deben solucionar primero. Pero con una aplicación de este tamaño, creo que caes en el territorio de "la necesidad de entender GC mucho más que el programador promedio".
Como han dicho otros, necesita perfilar su aplicación para ver dónde está el cuello de botella. ¿Su PermGen es demasiado grande para el espacio asignado? ¿Estás creando objetos innecesarios? jconsole funciona para mostrar al menos un mínimo de información sobre la máquina virtual. Es un punto de partida. Sin embargo, como otros han indicado, es muy probable que necesite herramientas más avanzadas que esta.
Buena suerte.
También sugeriría GCViewer y un generador de perfiles.
Ya que mencionas tu deseo de caché, supongo que la mayor parte de tu gran montón está ocupado por ese caché. Es posible que desee limitar el tamaño de la memoria caché para asegurarse de que nunca intente crecer lo suficiente como para llenar la generación permanente. No confíe solo en SoftReference
para limitar el tamaño. A medida que la generación anterior se llena de referencias suaves, las referencias antiguas se borrarán y se convertirán en basura. Se crearán nuevas referencias (tal vez a la misma información), pero se borrarán rápidamente porque hay poco espacio disponible. Eventualmente, el espacio ocupado está lleno de basura y necesita ser limpiado.
Considere ajustar la configuración de -XX:NewRatio
también. El valor predeterminado es 1: 2, lo que significa que un tercio del montón se asigna a la nueva generación. Para un gran montón, esto es casi siempre demasiado. Es posible que desee probar algo como 9, que mantendría 9 Gb de su montón de 10 Gb para la generación anterior.
Algunas cosas que espero puedan ayudar:
Nunca tuve mucha suerte con el ConcurrentCollector, en teoría sacrifica el rendimiento para obtener una latencia reducida, pero he encontrado mejor suerte con el colector de rendimiento tanto para el rendimiento como para la latencia (con ajuste y para mis aplicaciones) .
Su Cache of Soft References es una idea un poco peligrosa para los coleccionistas generacionales, y es probablemente una de las razones por las que sus colecciones de jóvenes no recogen demasiada basura.
Si no me equivoco, no importa cuán efímero sea un Objeto, si se pone en la memoria caché (que de seguro lo convirtió en la Generación Sostenida), estará vivo hasta que se lleve a cabo un FullGC, incluso si no hay otro ¡existen referencias a esto!
Lo que esto significa es que los objetos que viven en la generación joven que se colocan en el caché ahora se copian varias veces, se mantienen vivos, mantienen sus referencias con vida y, en general, ralentizan el GC de youngGen.
Es paradójico cómo el almacenamiento en caché puede reducir la asignación de objetos pero aumenta el tiempo de GC.
También es posible que desee intentar ajustar su proporción de supervivientes, ya que puede ser demasiado pequeño para desbordar aún más objetos ''jóvenes'' en la generación titular.
Aquí hay algunas cosas que he encontrado que pueden ser significativas.
- JSON-RPC puede generar una gran cantidad de objetos. No tanto como XML-RPC, pero todavía hay algo que observar. En cualquier caso, parece que está generando tanto a 100 MB de objetos por segundo, lo que significa que su GC está funcionando un alto porcentaje del tiempo y es probable que esté agregando a su latencia aleatoria. A pesar de que el GC es concurrente, es muy probable que su hardware / SO muestre latencia aleatoria no ideal bajo carga.
- Eche un vistazo a la arquitectura de su banco de memoria. En Linux, el comando es numactl --hardware. Si su máquina virtual se divide en más de un banco de memoria, esto aumentará significativamente sus tiempos de GC. (También ralentizará su aplicación, ya que estos accesos pueden ser significativamente menos eficientes). Cuanto más trabaje el subsistema de memoria, más probabilidades tendrá el sistema operativo de cambiar la memoria (a menudo en grandes cantidades) y obtendrá pausas dramáticas como resultado ( 100 ms no es sorprendente). No olvide que su sistema operativo hace más que solo ejecutar su aplicación.
- Considere la posibilidad de compactar / reducir el consumo de memoria de su caché. Si está utilizando múltiples GB de caché, vale la pena buscar formas de reducir el consumo de memoria más de lo que ya lo hizo.
- Sugiero que perfile su aplicación con el seguimiento de la asignación de memoria Y el muestreo de la CPU al mismo tiempo. Esto puede arrojar resultados muy diferentes y, a menudo, apunta a la causa de este tipo de problemas.
Usando estos enfoques, la latencia de una llamada RPC puede reducirse a menos de 200 microsegundos y los tiempos del GC reducidos a 1-3 ms efectuando menos de 1/300 de llamadas.
Resulta que esa parte del montón se intercambiaba al disco, por lo que la recolección de basura tuvo que sacar de la memoria una gran cantidad de datos del disco.
Resolví esto estableciendo el parámetro "Swappiness" de Linux en 0 (para que no intercambiara datos en el disco).
Personalmente no he usado un montón tan grande, pero he experimentado una latencia muy baja en general usando los siguientes switches para Oracle / Sun Java 1.6.x:
-Xincgc -XX:+UseConcMarkSweepGC -XX:CMSIncrementalSafetyFactor=50
-XX:+UseParNewGC
-XX:+CMSConcurrentMTEnabled -XX:ConcGCThreads=2 -XX:ParallelGCThreads=2
-XX:CMSIncrementalDutyCycleMin=0 -XX:CMSIncrementalDutyCycle=5
-XX:GCTimeRatio=90 -XX:MaxGCPauseMillis=20 -XX:GCPauseIntervalMillis=1000
Las partes importantes son, en mi opinión, el uso de CMS para la generación permanente y ParNewGC para la generación joven. Además, esto agrega un factor de seguridad bastante grande para CMS (el valor predeterminado es 10% en vez de 50%) y solicita tiempos de pausa cortos. Como tiene como objetivo un tiempo de respuesta de 25 ms, intento configurar -XX:MaxGCPauseMillis
en un valor aún menor. Incluso podría intentar usar más de dos núcleos para GC concurrente, pero supongo que no vale la pena el uso de la CPU.
Probablemente también deberías consultar la hoja de trucos de HotSpot JVM GC .