Reducción del tiempo de pausa de JVM> 1 segundo con UseConcMarkSweepGC


10

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 ejecutando en CentOS versión 5.2 (Final). Exactos detalles 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 de lanzar la aplicación con las siguientes opciones de línea de comandos:

java -XX:+UseConcMarkSweepGC -verbose:gc -server -Xmx10g -Xms10g ... 

Mi solicitud expone una API JSON-RPC, y mi objetivo es responder a las solicitudes dentro de 25 ms. Lamentablemente, veo retrasos que superan el 1 segundo y parece ser causado por la recolección de basura. Éstos 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 fue acompañada por una respuesta retardada API de duración muy similar a la longitud de la recolección de basura se muestra (dentro de unos pocos ms).

Éstos son algunos ejemplos típicos (éstos se produjeron todo en unos 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 el UseConcMarkSweepGC sería evitar esto, o al menos que sea 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 tener en cuenta que la mayor parte de la memoria está ocupada por una memoria caché LRU que hace uso de referencias suaves.

Cualquier asistencia o consejo sería muy apreciado.

+1

¿Qué JDK/JRE y qué sistema operativo? 22 feb. 092009-02-22 00:40:22

+1

Disculpa, solo agregué esa información 22 feb. 092009-02-22 00:43:58

6

Resulta que parte del montón se intercambiaba en el disco, por lo que la recolección de basura tuvo que sacar una gran cantidad de datos del disco a la memoria.

Lo resolví estableciendo el parámetro "swappiness" de Linux en 0 (para que no intercambiara datos en el disco).

+3

Me doy cuenta de que esto es de hace mucho tiempo, pero para los visitantes futuros: en un montón de este tamaño, considere habilitar enormes páginas para Java. Hugepages tampoco son intercambiables, por lo que resolverán el problema de intercambio. 04 oct. 112011-10-04 14:43:08


0

Algunos lugares para empezar a buscar:

También me gustaría correr el código a través de un generador de perfiles .. me gusta el de NetBeans, pero hay otros también. 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).


0

También sugeriría GCViewer y un generador de perfiles.


11

Primero, consulte la documentación Java SE 6 HotSpot[tm] Virtual Machine Garbage Collection Tuning, si aún no lo ha hecho. Esta documentación dice:

el colector concurrentes hace la mayor parte de su trazado y el trabajo de barrido con las hebras de la aplicación aún en marcha, por lo que sólo pausas breves son vistos por las hebras de la aplicación .Sin embargo, si el recopilador concurrente no puede finalizar recuperando los objetos inalcanzables antes de que se llene la generación permanente, o si no se puede satisfacer una asignación con los bloques de espacio libre disponibles en la generación de tenencia , la aplicación se pausa y la colección se completa con todos los subprocesos de la aplicación detenidos. La imposibilidad de completar una recopilación concurrentemente se conoce como falla de modo concurrente e indica la necesidad de ajustar los parámetros de colector simultáneos.

y un poco más adelante ...

El colector concurrentes se detiene una aplicación dos veces durante un ciclo de recogida simultánea .

Noto 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 montón enorme montón de muchos estándares, e ingenuamente esperaría que GC tome 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.

Normalmente, le digo a alguien que si tienen que sintonizar GC, entonces 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.


1

Aquí hay algunas cosas que he encontrado que podrían ser importantes.

  • JSON-RPC puede generar muchos 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 el perfil de su aplicación con seguimiento de asignación de memoria Y muestreo de CPU al mismo tiempo. Esto puede arrojar resultados muy diferentes y, a menudo, apunta a la causa de este tipo de problemas.

El uso de estos enfoques, la latencia de una llamada RPC se puede reducir a por debajo de 200 micro-segundo y los tiempos de GC reducido a 1-3 ms que afectan menos de 1/300 de llamadas.


11

Como 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 también la configuración -XX:NewRatio. 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 10   Gb montón para la generación anterior.

+1

+1. Ese es un muy buen punto sobre -XX: NewRatio. 22 feb. 092009-02-22 03:25:04

  0

Sí, creo que este es algo que todos extrañamos al sintonizar. 21 oct. 102010-10-21 08:37:21


0

Un par de cosas que espero que pueda ayudar:

nunca he tenido mucha suerte con el ConcurrentCollector, en teoría, se sacrifica el rendimiento para la ganancia de latencia reducida, pero he encontrado mejor suerte con el colector de rendimiento para rendimiento y 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 generaciones no recogen demasiada basura.

Si no me equivoco, no importa qué tan efímero sea un Objeto, si se pone en la memoria caché (lo que de seguro se convirtió en la Generación Sostenida), estará vivo hasta que se lleve a cabo un FullGC, ¡incluso si no existen otras referencias a él!

Lo que esto significa es que sus objetos que viven en la generación joven que se colocan en la memoria caché ahora se copian varias veces, se mantienen vivos, mantienen sus referencias vivas, y en general ralentizan el GC 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.


0

No he utilizado personalmente a un enorme montón tal, sino que he experimentado una latencia muy baja, en general utilizando modificadores siguientes de 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 su objetivo es un tiempo de respuesta de 25 ms, intentaría establecer -XX:MaxGCPauseMillis en un valor aún menor.Incluso podría tratar de usar más de dos núcleos para GC concurrente, pero me gustaría suponer que no vale la pena el uso de la CPU.

Probablemente también deba consultar el HotSpot JVM GC cheat sheet.