Recolector de basura de Java: no se ejecuta normalmente a intervalos regulares

Tengo un progtwig que se ejecuta constantemente. Normalmente, parece que se recolecta basura, y permanece bajo aproximadamente 8MB de uso de memoria. Sin embargo, cada fin de semana, se niega a recolectar basura a menos que yo haga una llamada explícita a ella. Sin embargo, si se acerca al tamaño máximo de almacenamiento dynamic, seguirá recolectando basura. Sin embargo, la única razón por la que se notó este problema, es porque en realidad se bloqueó al quedarse sin memoria en un fin de semana, es decir, debe haber alcanzado el tamaño máximo del montón y no ejecutar el recolector de basura.

La siguiente imagen (haga clic para ver) es un gráfico del uso de memoria del progtwig durante un día. En los lados del gráfico, puede ver el comportamiento normal del uso de la memoria del progtwig, pero el primer pico grande es lo que parece comenzar el fin de semana. Este gráfico en particular es un ejemplo extraño, porque después de hacer una llamada explícita al recolector de basura, se ejecutó con éxito, pero luego volvió a subir al tamaño máximo de stack y la basura se recolectó con éxito por sí sola dos veces.

¿Que esta pasando aqui?

EDITAR:

Ok, de los comentarios, parece que no he proporcionado suficiente información. El progtwig simplemente recibe un flujo de paquetes UDP, que se colocan en una cola (configurado para tener un tamaño máximo de 1000 objetos), que luego se procesan para que sus datos se almacenen en una base de datos. En promedio, recibe alrededor de 80 paquetes por segundo, pero puede llegar a 150. Se está ejecutando en Windows Server 2008.

La cuestión es que esta actividad es bastante constante y, en todo caso, en el momento en que se inicia el uso de la memoria, se incrementa constantemente, la actividad debe ser más baja, no más alta. Tenga en cuenta que el gráfico que publiqué arriba es el único que tengo tan atrás, ya que solo cambié el contenedor Java Visual VM para mantener los datos del gráfico lo suficientemente lejos como para verlos esta semana, así que no tengo idea si es exactamente la misma hora todas las semanas, porque no puedo verlo durante el fin de semana, ya que está en una red privada y no estoy trabajando en el fin de semana.

Aquí hay una gráfica del día siguiente: texto alternativo

Esto es más o menos como se ve el uso de la memoria cada dos días de la semana. El progtwig nunca se reinicia y solo se lo contamos a la recolección de basura un lunes por la mañana debido a este problema. Una semana intentamos reiniciarlo un viernes por la tarde, y aún así comenzó a escalar en algún momento durante el fin de semana, por lo que el momento en que lo reiniciamos no parece tener nada que ver con el uso de la memoria la próxima semana.

El hecho de que la basura recolecte con éxito todos esos objetos cuando le decimos que me implique que los objetos son coleccionables, simplemente no lo está haciendo hasta que alcanza el tamaño máximo del montón, o llamamos explícitamente al recolector de basura. Un volcado de almacenamiento no nos dice nada, porque cuando intentamos realizar uno, de repente ejecuta el recolector de basura, y luego genera un volcado de almacenamiento, que por supuesto se ve perfectamente normal en este punto.

Así que supongo que tengo dos preguntas: ¿Por qué de repente no es la recolección de basura la forma en que lo hace el rest de la semana, y por qué en una ocasión, la recolección de basura que se produce cuando alcanza el tamaño máximo del montón no se pudo recolectar? todos esos objetos (es decir, ¿por qué habría referencias a tantos objetos que una vez, cuando no debe haber)?

ACTUALIZAR:

Esta mañana ha sido muy interesante. Como mencioné en los comentarios, el progtwig se está ejecutando en el sistema de un cliente. Nuestro contacto en la organización cliente informa que a la 1 am, este progtwig falló, y tuvo que reiniciarlo manualmente cuando entró al trabajo esta mañana, y una vez más, la hora del servidor era incorrecta. Este es un problema que hemos tenido con ellos en el pasado, pero hasta ahora, el problema nunca parecía estar relacionado.

Mirando a través de los registros que produce nuestro progtwig, podemos deducir la siguiente información:

  1. A la 01:00, el servidor ha vuelto a sincronizar su hora, configurándolo a 00:28.
  2. A las 00:45 (según la nueva hora incorrecta del servidor), uno de los subprocesos de procesamiento de mensajes en el progtwig generó un error de falta de memoria.
  3. Sin embargo, el otro subproceso de procesamiento de mensajes (hay dos tipos de mensajes que recibimos, se procesan de manera ligeramente diferente, pero los dos entran constantemente), continúa ejecutándose y, como es habitual, el uso de la memoria sigue aumentando sin recolección de basura (como se ve en los gráficos que hemos estado grabando, una vez más).
  4. A las 00:56, los registros se detienen hasta aproximadamente las 7 am cuando nuestro cliente reinició el progtwig. Sin embargo, el gráfico de uso de memoria, para este tiempo, seguía aumentando constantemente.

Desafortunadamente, debido al cambio en la hora del servidor, esto hace que los tiempos en nuestro gráfico de uso de memoria no sean confiables. Sin embargo, parece ser que trató de recolectar basura, falló, aumentó el espacio de almacenamiento al tamaño máximo disponible y eliminó ese hilo de una vez. Ahora que el espacio de almacenamiento dynamic máximo ha aumentado, está feliz de usarlo todo sin realizar una gran recolección de basura.

Entonces ahora pregunto esto: si la hora del servidor cambia repentinamente como lo hizo, ¿puede eso causar un problema con el proceso de recolección de basura?

Sin embargo, la única razón por la que se notó este problema, es porque en realidad se bloqueó al quedarse sin memoria en un fin de semana, es decir, debe haber alcanzado el tamaño máximo del montón y no ejecutar el recolector de basura.

Creo que su diagnóstico es incorrecto. A menos que haya algo seriamente roto en su JVM, entonces la aplicación solo lanzará un OOME después de que solo haya ejecutado una recolección de basura completa, y descubrió que todavía no tiene suficiente montón libre para continuar * .

Sospecho que lo que está sucediendo aquí es uno o más de los siguientes:

  • Su aplicación tiene una pérdida de memoria lenta. Cada vez que reinicia la aplicación, la memoria perdida se recupera. Entonces, si reinicia la aplicación regularmente durante la semana, esto podría explicar por qué solo se bloquea el fin de semana.

  • Su aplicación está haciendo cálculos que requieren diferentes cantidades de memoria para completar. En ese fin de semana, alguien le envió una solicitud que requería más memoria disponible.

Ejecutar el GC a mano no solucionará el problema en ningún caso. Lo que debe hacer es investigar la posibilidad de memory leaks y también observar el tamaño de la memoria de la aplicación para ver si es lo suficientemente grande para las tareas que se están realizando.

Si puede capturar estadísticas de almacenamiento dynamic durante un largo período, una pérdida de memoria se mostrará como una tendencia descendente a lo largo del tiempo en la cantidad de memoria disponible después de las recolecciones de basura completas. (Esa es la altura de los “dientes” más largos del patrón de diente de sierra.) La escasez de memoria relacionada con la carga de trabajo probablemente se mostrará como una tendencia bajista ocasional en la misma medida durante un período de tiempo relativamente corto, seguida de una recuperación. Puedes ver ambas cosas, entonces podrías tener ambas cosas sucediendo.

* En realidad, los criterios para decidir cuándo renunciar a un OOME son un poco más complicados que esto. Dependen de ciertas opciones de ajuste de JVM y pueden incluir el porcentaje de tiempo empleado en ejecutar el GC.

SEGUIR

@Ogre – Necesitaría mucha más información sobre su aplicación para poder responder a esa pregunta (sobre las memory leaks) con cualquier especificidad.

Con su nueva evidencia, hay dos posibilidades más:

  • Es posible que su aplicación se esté atascando en un bucle que pierde memoria como resultado de la distorsión horaria del reloj.

  • La distorsión de tiempo del reloj puede hacer que el GC piense que está tomando un porcentaje de tiempo de ejecución demasiado grande y, como resultado, desencadenar un OOME. Este comportamiento depende de su configuración de JVM.

De cualquier manera, debe apoyarse con fuerza en su cliente para que deje de ajustar el reloj del sistema de esa manera. (¡Un tiempo de 32 minutos es demasiado!). Pídales que instalen un servicio del sistema para mantener el reloj sincronizado con la hora de la red hora por hora (o más frecuente). De manera crítica, haga que utilicen un servicio con una opción para ajustar el reloj en pequeños incrementos.

(Con respecto a la segunda viñeta: hay un mecanismo de monitoreo de GC en la JVM que mide el porcentaje del tiempo total que la JVM invierte en ejecutar el GC, en relación con el trabajo útil. Está diseñado para evitar que la JVM se detenga cuando Su aplicación se está quedando realmente sin memoria.

Este mecanismo se implementaría muestreando el tiempo del reloj de pared en varios puntos. Pero si el tiempo del reloj de pared se tergiversa en un momento crítico, es fácil ver cómo la JVM puede pensar que una ejecución de GC en particular tomó mucho más tiempo de lo que realmente lo hizo … y dispara el OOME.

Si es posible, configuraría el proceso para volcar el montón si se queda sin memoria, para que pueda analizarlo si (cuando) vuelve a suceder. No es una respuesta, sino una ruta potencial a una solución.

Estas son las opciones de JVM, tomadas de la página de opciones de Oracle HotSpot VM de Oracle. (Esto supone que tienes una JVM de Oracle):

-XX: HeapDumpPath =. / Java_pid.hprof

Ruta al directorio o nombre de archivo para volcado de stack. Manejable. (Introducido en 1.4.2 actualización 12, 5.0 actualización 7.)

-XX: -HeapDumpOnOOutOfMemoryError

Volcar el montón al archivo cuando se lanza java.lang.OutOfMemoryError. Manejable. (Introducido en 1.4.2 actualización 12, 5.0 actualización 7.)

Ok chicos, gracias por toda su ayuda. La respuesta correcta, sin embargo, resultó no tener nada que ver con el progtwig en sí.

Parece que en el momento en que comenzó el uso de la memoria es una escalada constante, el servidor estaba sincronizando su tiempo desde algún lugar interno, aunque el contacto de TI de nuestro cliente no tiene idea de dónde. Obviamente, de donde venía, no era un buen reloj, ya que la hora estaba atrasada media hora. Desactivamos esta sincronización, y ahora que lo he comprobado de nuevo esta mañana, el problema no se produjo. Entonces, si el tiempo en su sistema cambia repentinamente, aparentemente esto causa problemas para el recolector de basura. Al menos eso es lo que esto me implica.

En cuanto a por qué esto no sucedió en ninguna otra parte de nuestro sistema en este servidor (que también está escrito en Java), es probable que simplemente no lo hayamos notado, ya que no se ocupan de una cantidad tan grande de objetos, y por lo que nunca habrían alcanzado su estado de memoria.

Esto me parece extraño, ya que habría pensado que la invocación del recolector de basura estaría completamente relacionada con el uso de la memoria, y no en el tiempo del sistema. Claramente, mi comprensión de cómo funciona el recolector de basura es lamentablemente inadecuada.