2010-05-06 25 views
17

Estoy buscando argumentos sobre cómo dimensionar mejor la generación joven (con respecto a la generación anterior) en un entorno donde la baja latencia es crítica.Ajuste de colecciones de basura para baja latencia

Mis propias pruebas tienden a mostrar que la latencia es más baja cuando la generación joven es bastante grande (por ejemplo, -XX: NewRatio < 3), sin embargo, no puedo conciliar esto con la intuición de que cuanto mayor sea la generación joven, debería llevar a recoger basura.

La aplicación se ejecuta en Linux de 64 bits, JDK 6.

uso de memoria es de aproximadamente 50Megabytes de objetos de larga vida que se carga en el arranque (caché = datos), y desde allí es solamente (muchos) muy corta duración objetos creados (con una vida media de < 1 milisegundo).

Algunos ciclos de recolección de basura tardan más de 10 milisegundos en ejecutarse ... lo cual se ve realmente desproporcionado en comparación con la latencia de la aplicación, que de nuevo es de unos pocos milisegundos al máximo.

+0

Si la generación joven es grande, ¿no significaría eso que los objetos con vidas semi-largas desaparecen en recolecciones baratas de generaciones jóvenes, dándole menos recolecciones costosas de la vieja generación? – gustafc

+1

@elec: No puedo ayudarlo aquí ... Pero su pregunta es sintomática de un problema fundamental con Java y su administración de memoria "automatizada" (vea cómo está automatizada ahora uh). Cuanto más utilizo Java (hace diez años), más me gustaría poder administrar la memoria yo mismo (y, sí, he trabajado con lenguajes que obligan a la gestión manual de la memoria, incluidos los lenguajes de segunda generación [que entran directamente en códigos hexadecimales], hace décadas). ¿Cuánto desperdicio de energía y tiempo en el mundo de Java tratando de entender y "afinar" ese GC no determinista? Para lo que no debería ser un problema: me da ganas de volver a C++. – SyntaxT3rr0r

+0

@WizardOfOdds - Estoy de acuerdo contigo ... aunque me gusta Java, parece que esta no es la herramienta adecuada para usar en un entorno en el que se requieren latencias muy bajas. – Eleco

Respuesta

14

Para una aplicación que genera mucha basura de corta vida y nada duradero, un enfoque que puede funcionar es un gran montón con casi todo el gen y casi todo lo que sobrevive a una colección YG más de una vez.

Por ejemplo (digamos que usted tenía una JVM de 32 bits)

  • 3072M montón (XMS y XmnI)
  • 128M titular (es decir,2944m XMN)
  • MaxTenuringThreshold = 1
  • SurvivorRatio = 190 (es decir, cada espacio sobreviviente es 1/192 de la YG)
  • TargetSurvivorRatio = 90 (es decir, llenar esos supervivientes tanto como sea posible)

Los parámetros exactos que usaría para esta configuración dependen de cuál sea el tamaño de estado estable de su conjunto de trabajo (es decir, cuánto está vivo en el momento de cada colección). El pensamiento aquí obviamente va en contra de las reglas normales de tamaño de montón, pero entonces no tienes una aplicación que se comporte de esa manera. La idea es que la aplicación es principalmente basura de corta duración y un poco de datos estáticos, por lo tanto, configure el jvm para que los datos estáticos se conserven rápidamente y luego tenga un YG lo suficientemente grande para que no se recopile v a menudo minimizando la frecuencia de las pausas. Tendría que girar las perillas repetidas veces para determinar qué tamaño es bueno para usted & cómo se equilibra con el tamaño de la pausa que recibe por colección. Es posible que encuentre pausas YG más cortas pero más frecuentes, por ejemplo.

No dice cuánto tiempo se ejecuta su aplicación, pero el objetivo aquí es no tener colecciones permanentes durante la vida de la aplicación. Esto puede ser imposible, por supuesto, pero vale la pena apuntar.

Sin embargo, no es solo el problema de la colección lo que es importante en su caso, es donde se asigna la memoria. El recopilador NUMA (solo compatible con el colector de rendimiento y activado con el conmutador UseNUMA) hace uso de la observación de que un objeto a menudo se utiliza exclusivamente por el hilo que lo creó &, por lo que asigna memoria en consecuencia. No estoy seguro de en qué se basa en Linux pero usa MPO (optimización de ubicación de memoria) en Solaris, some details on one of the GC guys blogs

Dado que está utilizando 64bit jvm, asegúrese de estar utilizando también CompressedOops.

Teniendo en cuenta la tasa de asignación de objetos (posiblemente algún tipo de lib de ciencia) y la duración de la vida, entonces debe prestar cierta atención a la reutilización de objetos. Un ejemplo de un lib hacer esto es la javalution StackContext

Por último vale la pena señalar que las pausas GC no son la única STW hace una pausa, usted podría funcionar con la versión 6u21 early access que tiene algunas correcciones a los interruptores PrintGCApplicationStoppedTime y PrintGCApplicationConcurrentTime (que efectivamente impresión tiempo en un punto seguro global y tiempo entre esos puntos de seguridad). Puede usar el indicador tracesafepointstatistics para hacerse una idea de lo que está causando que necesite un punto de seguridad (es decir, ningún subproceso está ejecutando código byte).

5

¿Ha habilitado ya configuraciones de GC más relevantes, como seleccionar un algoritmo de colector de pausa baja concurrente?

En general, las generaciones jóvenes, permanentes y permanentes deben dimensionarse para que coincida con el perfil de su aplicación. Si tienes muchos objetos efímeros pero los más jóvenes son demasiado pequeños, muchos objetos se mantendrán en propiedad, forzando colecciones importantes más frecuentes de toda la generación titular. Del mismo modo, si el joven es demasiado grande, entonces la tenencia es necesariamente menor, y puede obligar a frecuentes colecciones importantes de tenencia.

Hablando en términos prácticos, creo que encontrará que el tiempo dedicado a las colecciones menores frente a las principales se intercambia a medida que aumenta el tamaño de la generación joven, y es óptimo en algún momento.

Tal vez sea útil observar que en las aplicaciones de servidor "sensibles" al rendimiento, he encontrado que es necesario reducir la generación joven, en general. Esto se debe a que dichas aplicaciones ya debieron haber sido perfiladas para zonas activas de asignación de memoria y optimizadas, por lo que están produciendo pocos objetos de corta duración. Esto a su vez significa que la generación joven está acaparando demasiado del montón.

Supongo que primero haré esa optimización, luego veré la aparición de NewRatio más allá de 8, y veré la salida dada por -verbose: gc para ver cómo se desactiva el tiempo de GC y GC completo y dónde es óptimo.

+0

FYI: Busqué cómo habilitar el recopilador de pausa baja mencionado anteriormente y encontré esta información aparentemente relavante: http://java.sun.com/docs/hotspot/gc5.0/gc_tuning_5.html#1.1.% 20Tipos% 20de% 20Collectores | esquema Consulte el elemento n.º 2 debajo del ancla en el enlace. Dice usar "-Xincgc" o "-XX: + UseConcMarkSweepGC" –

1

Cuando intente aplicaciones en tiempo real con Java, la sintonización de recolección de basura es esencial, pero también hay otros aspectos que debe tener en cuenta (por ejemplo, el compilador JIT, temporizadores, subprocesamiento, manejo asincrónico de eventos).

Dado que parece haber una demanda de Java en tiempo real, Sun proporciona una especificación del Sistema en tiempo real de Java y tiene una implementación comercial disponible. Puede encontrar más información here.

Cuestiones relacionadas