Muy extraño OutOfMemoryError

Como siempre, una larga descripción del problema.

Actualmente estamos haciendo pruebas de estrés con nuestro producto, y ahora nos enfrentamos a un problema extraño. Después de una o dos horas, el espacio de almacenamiento dinámico comienza a crecer, la aplicación muere algún tiempo después.

La creación de perfiles de la aplicación muestra una gran cantidad de objetos Finalizer, que llenan el montón. Bueno, pensamos que "podría ser el tema del finalizador extraño para ralentizar" y revisamos la reducción de la cantidad de objetos que deben finalizarse (identificadores nativos de JNA en este caso). Buena idea de todos modos y redujo miles de nuevos objetos...

Las siguientes pruebas mostraron el mismo patrón, solo una hora más tarde y no tan empinadas. Esta vez, los Finalizadores se originaron a partir de los flujos FileInput y FileOutput que se utilizan mucho en el banco de pruebas. Todos los recursos están cerrados, pero los Finalizadores ya no se limpian.

No tengo idea de por qué después de 1 o 2 horas (sin excepciones) el FinalizerThread parece dejar de funcionar repentinamente. Si forzamos System.runFinalization() a mano en algunos de nuestros subprocesos, el generador de perfiles muestra que los finalizadores están limpios. Reanudar la prueba provoca inmediatamente una nueva asignación de almacenamiento dinámico para los Finalizadores.

El FinalizerThread todavía está allí, preguntando a jConsole que está ESPERANDO.

EDITAR

Primero, inspeccionar el montón con HeapAnalyzer no reveló nada nuevo/extraño. HeapAnalyzer tiene algunas características interesantes, pero al principio tuve mis dificultades. Estoy usando jProfiler, que viene junto con buenas herramientas de inspección de montones y se quedará con él.

¿Tal vez me estoy perdiendo algunas funciones increíbles en HeapAnalyzer?

En segundo lugar, hoy configuramos las pruebas con una conexión de depuración en lugar del generador de perfiles: el sistema está estable durante casi 5 horas. Esta parece ser una combinación muy extraña de demasiados Finalizadores (que se han reducido en la primera revisión), el generador de perfiles y las estrategias de VM GC. Como todo funciona bien en este momento, no hay ideas reales...

Gracias por el aporte hasta ahora, tal vez permanezca atento e interesado (ahora que puede tener más razones para creer que no hablamos de una simple falla de programación).

preguntado el 03 de mayo de 12 a las 18:05

Los finalizadores no son realmente fiables. Evítales. -

@Charles lo hice, gracias. Creo (pensé) que soy lo suficientemente fluido con los detalles de GC (hasta ahora). Y aquí ni siquiera estoy implementando "finalizar" - hablamos de flujos de archivos... -

@Louis Temía que alguien dijera esto :-) Se lo reenviaré a Oracle... -

6 Respuestas

Quiero cerrar esta pregunta con un resumen del estado actual.

La última prueba ahora ha durado más de 60 horas sin ningún problema. Eso nos lleva al siguiente resumen/conclusiones:

  • Tenemos un servidor de alto rendimiento que usa muchos objetos que al final implementan "finalizar". Estos objetos son en su mayoría identificadores de memoria JNA y flujos de archivos. Construyendo los finalizadores más rápido de lo que GC y el subproceso del finalizador pueden limpiar, este proceso falla después de ~ 3 horas. Este es un fenómeno bien conocido (-> google).
  • Hicimos algunas optimizaciones para que el servidor se deshiciera de casi todos los JNA Finalizers. Esta versión se probó con jProfiler adjunto.
  • El servidor murió algunas horas después de nuestro intento inicial...
  • El generador de perfiles mostró una gran cantidad de finalizadores, esta vez causados ​​principalmente por flujos de archivos. Esta cola no se limpió, incluso después de pausar el servidor durante algún tiempo.
  • Solo después de activar manualmente "System.runFinalization()", la cola se vació. Reanudando el servidor comenzó a recargar...
  • Esto sigue siendo inexplicable. Ahora suponemos que se trata de alguna interacción del generador de perfiles con GC/finalización.
  • Para depurar cuál podría ser el motivo del subproceso del finalizador inactivo, desconectamos el generador de perfiles y adjuntamos el depurador esta vez.
  • El sistema funcionaba sin defectos notables... FinalizerThread y GC todos "verdes".
  • Reanudamos la prueba (ahora por primera vez nuevamente sin ningún otro agente además de jConsole adjunto) y está en funcionamiento durante más de 60 horas. Entonces, aparentemente, la refactorización inicial de JNA resolvió el problema, solo la sesión de creación de perfiles agregó algo de indeterminismo (saludos de Heisenberg).

Otras estrategias para gestionar los finalizadores se analizan, por ejemplo, en http://cleversoft.wordpress.com/2011/05/14/out-of-memory-exception-from-finalizer-object-overflow/ (además del no demasiado inteligente "no use finalizadores"..).

Gracias por todas sus aportaciones.

contestado el 07 de mayo de 12 a las 11:05

Es difícil dar una respuesta específica a su dilema, pero tome un volcado de pila y ejecútelo a través de HeapAnalyzer de IBM. Busque "analizador de montón en: http://www.ibm.com/developerworks (el enlace directo sigue cambiando). Parece muy poco probable que el hilo del finalizador "deje de funcionar de repente" si no está anulando finalizar.

contestado el 03 de mayo de 12 a las 18:05

"Si eliminas lo imposible, lo que quede, por improbable que sea, debe ser la verdad"... ¿a qué conclusión debo llegar? - mtraut

¿Estás diciendo "No sé cómo usar HeapAnalyzer". ? - Java42

No, solo estoy desesperado... Y cuando dices "es poco probable..." eso me recordó la gran cita de Spock :-) Lo intentaré mañana... - mtraut

De acuerdo: creo que al final descubrirá que hay un error en su producto. Solo sugiero que mire los objetos en el montón para ayudarlo a identificar el código que está asignando los objetos que no se están limpiando. HeapAnalyzer es una gran herramienta para identificar posibles fugas. - Java42

Realmente no lo creo, pero lo intentaré. Recuerde, la cola se vacía cuando se activa a mano. No hay fuga en el sentido de que algún objeto es referenciado por algún camino oscuro. Ni la inspección de código ni el generador de perfiles muestran otras raíces de GC pero el Finalizador... - mtraut

Es posible que el Finalizer se bloquee, pero no sé cómo podría simplemente morir.

Si tiene muchos métodos FileInputStream y FileOutputStream finalize(), esto indica que no está cerrando sus archivos correctamente. Asegúrese de que estas secuencias estén siempre cerradas en un bloque finalmente o use ARM de Java 7. (Administración automática de recursos)

jConsole está ESPERANDO.

Para estar ESPERANDO tiene que estar esperando un objeto.

contestado el 03 de mayo de 12 a las 18:05

Esto parece obvio, pero te puedo asegurar que no es así. El banco de pruebas utiliza métodos de herramientas maduros, todos ellos cerrando bloques finalmente. La prueba funciona absolutamente sin fugas durante horas antes de degenerar. Además, no entiendo bien la afirmación "si tiene... métodos finalize()". No tengo métodos de finalización: las secuencias sí los tienen... y los finalizadores creados para ellas no se limpian... y cuando está en el generador de perfiles, las referencias del descriptor de la secuencia se restablecen a -1 (por lo que están cerrados) - mtraut

Quiero decir, si estas clases finalizan, los métodos se muestran como significativos. Por lo que sugiere, si aparecen es porque algo más está retrasando la cola o el hilo del finalizador ya no es el problema. - pedro laurey

Si está esperando la cola, eso sugiere que está vacío. - pedro laurey

Esto es lo que uno debe suponer. Pero, cuando - No hay nada en la cola - Hay muchos Finalizadores - Hacen referencia a objetos que de otro modo no estarían referenciados, ¿quién tiene la culpa? - mtraut

Los objetos a los que hacen referencia objetos que deben finalizarse primero, solo se pueden limpiar en el próximo ciclo de GC. - pedro laurey

Tanto FileInputStream como FileOutputStream tienen el mismo comentario en sus métodos finalize():

. . .
/*
 * Finalizer should not release the FileDescriptor if another
 * stream is still using it. If the user directly invokes
 * close() then the FileDescriptor is also released.
 */
     runningFinalize.set(Boolean.TRUE); 
. . . 

lo que significa que su Finalizer puede estar esperando que se publique la transmisión. Lo que significa que, como Joop Eggen mencionó anteriormente, su aplicación puede estar haciendo algo mal al cerrar una de las transmisiones.

contestado el 03 de mayo de 12 a las 19:05

este no es el caso cuando miro en mi JDK 1.6.21. Que versión usas? - mtraut

Voy a revisar esto una vez más. Pero no veo cómo esto puede explicar el hambre de la finalización. Recuerde, llamar a runFinalization a mano limpia la cola. - mtraut

Mi conjetura: es un cierre anulado en sus propias clases de flujo (envoltorio). Como las clases de flujo a menudo son envoltorios y delegan a otros, podría imaginar que tal anidado new A(new B(new C())) podría causar alguna lógica incorrecta al cerrar. Debe buscar dos veces el cierre, el cierre del delegado. Y tal vez todavía algún cierre olvidado (¿cierre en el objeto equivocado?).

contestado el 03 de mayo de 12 a las 18:05

Extraño, ¿quizás algo totalmente diferente, como múltiples accesos/eliminaciones/creaciones al mismo nombre de archivo? Eso también podría causar cierta inestabilidad, especialmente en Windows. - joop eggen

Con un montón de crecimiento lento, el recolector de basura de Java puede quedarse sin memoria cuando intenta recolectar basura tardíamente en una situación de poca memoria. Intente activar la marca simultánea y la recolección de basura de barrido con -XX: + UseConcMarkSweepGC y vea si su problema desaparece.

contestado el 03 de mayo de 12 a las 18:05

el montón es activo - no de crecimiento lento. Hay una gran cantidad de recolección de basura durante la ejecución. Solo después de una hora, los Finalizadores ya no se recopilan. - mtraut

Incluso en un montón activo, Java a menudo aplazará la recolección de elementos no utilizados de algunos objetos, lo que puede explicar los problemas con el código del finalizador. - Michael Shopsin

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.