2012-05-03 17 views
12

Como siempre, una larga descripción del problema.Muy extraño OutOfMemoryError

Actualmente estamos poniendo a prueba nuestro producto, y ahora nos enfrentamos a un problema extraño. Después de una o dos horas, el espacio del montón comienza a crecer, la aplicación muere algún tiempo después.

El perfil de la aplicación muestra una gran cantidad de objetos Finalizer, llenando el montón. Bien, pensamos que el problema "podría ser el hilo finalizador extraño para ralentizar" y se revisó para reducir la cantidad de objetos que se deben finalizar (los manejadores nativos de JNA en este caso). Buena idea de todos modos y reducido miles de objetos nuevos ...

Las siguientes pruebas mostraron el mismo patrón, solo una hora más tarde y no tan pronunciadas. Esta vez, los Finalizer se originaron a partir de las secuencias FileInput y FileOutput que se usan mucho en el banco de pruebas. Todos los recursos están cerrados, pero los Finalizers ya no se limpiaron.

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 hilos, el generador de perfiles muestra que los finalizadores se limpian. Reanudar la prueba de inmediato causa una nueva asignación de montón para los Finalizadores.

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

EDITAR

En primer lugar, la inspección de la pila con HeapAnalyzer reveló nada nuevo/extraño. HeapAnalyzer tiene algunas características agradables, pero tuve mis dificultades al principio. Estoy usando jProfiler, que viene con buenas herramientas de inspección de montón y se quedará con eso.

¿Tal vez me faltan algunas funciones fatales en HeapAnalyzer?

En segundo lugar, hoy configuramos las pruebas con una conexión de depuración en lugar del perfilador: el sistema es estable durante casi 5 horas. Esta parece ser una combinación muy extraña de demasiados Finalizer (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 la contribución hasta ahora, tal vez permanezca atento e interesado (ahora que puede tener más razones para creer que no hablamos de una simple programación culpa).

+0

Puede que quiera dar http://java.sun.com/developer/technicalArticles/javase/finalization/ a read. – Charles

+0

Algunas ideas: http://stackoverflow.com/questions/8355064/is-memory-leak-why-java-lang-ref-finalizer-eat-so-much-memory – assylias

+0

Finalizers realmente no son confiables. Evítales. –

Respuesta

3

Quiero cerrar esta pregunta con un resumen del estado actual.

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

  • Tenemos un servidor de alto rendimiento que utiliza muchos objetos que al final implementan "finalizar". Estos objetos son en su mayoría controladores de memoria JNA y flujos de archivos. Construyendo los Finalizadores más rápido que el GC y el hilo del finalizador son capaces de 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 finalizadores de JNA. Esta versión fue probada con jProfiler adjunto.
  • El servidor murió unas horas después de nuestro intento inicial ...
  • El generador de perfiles mostró una gran cantidad de finalizadores, esta vez causados ​​principalmente solo por secuencias de archivos. Esta cola no se limpió, incluso después de pausar el servidor durante un tiempo.
  • Solo después de activar manualmente "System.runFinalization()", la cola se vació. Reanudar el servidor comenzó a rellenar ...
  • Esto todavía es inexplicable. Ahora suponemos que se trata de una interacción de generador de perfiles con GC/finalización.
  • Para depurar cuál podría ser el motivo del subproceso finalizador inactivo, separamos el generador de perfiles y adjuntamos el depurador esta vez.
  • El sistema se estaba ejecutando sin defectos notables ... FinalizerThread y GC todo "verde".
  • Reanudamos la prueba (ahora por primera vez nuevamente sin ningún agente aparte de jConsole) y ahora está funcionando bien por 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 discuten por ejemplo en http://cleversoft.wordpress.com/2011/05/14/out-of-memory-exception-from-finalizer-object-overflow/ (además de los "finalizadores de uso no demasiado ingeniosos").

Gracias por todas sus aportaciones.

1

Difícil de dar una respuesta específica a su dilema pero tome un volcado de pila y ejecútelo a través del HeapAnalyzer de IBM. Busque "Heap Analyzer" en: http://www.ibm.com/developerworks (el enlace directo cambia). Parece muy poco probable que el finalizador "deje de funcionar repentinamente" si no está anulando.

+0

"Si elimina lo imposible, lo que quede, aunque sea improbable, debe ser la verdad" ... ¿qué conclusión debo encontrar? – mtraut

+0

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

+0

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

1

Es posible que el Finalizer esté bloqueado, pero yo no 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 estos flujos estén siempre cerrados en un bloque finally o use ARM de Java 7. (Gestión automática de recursos)

jConsole Está ESPERANDO .

Para estar EN ESPERA tiene que estar esperando a un objeto.

+0

Esto parece obvio, pero puedo asegurarle que no es el caso. El banco de pruebas utiliza métodos de herramienta maduros, todos ellos cerrando finalmente en bloques. La prueba se ejecuta absolutamente sin fugas durante horas antes de degenerar. Además, no obtengo el enunciado "si tiene ... métodos de finalización()" correctos. No tengo métodos de finalización: las secuencias sí tienen ... y los Finalizer creados para ellos no se limpian ... y cuando están en el generador de perfiles, las referencias de descriptores de la secuencia se reinician a -1 (para que estén cerradas) – mtraut

+0

Bueno, el FinalizerThread está esperando la cola ... – mtraut

+1

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

0

Supongo que se trata de un cierre anulado en tus propias clases de flujo (contenedor).Como las clases de flujo a menudo son envoltorios y delegan a otros, podría imaginarse que un new A(new B(new C())) anidado así podría causar una lógica incorrecta al cerrar. Debe buscar dos veces cierre, cierre delegado. Y tal vez todavía algún cierre olvidado (¿cerca del objeto equivocado?).

+0

Todas las corrientes de archivos sin formato – mtraut

+0

¿Extraño, tal vez algo totalmente diferente, como múltiples accesos/eliminaciones/crea el mismo nombre de archivo? Eso también puede causar inestabilidad, especialmente en Windows. –

0

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 concurrente y barrer la recolección de basura con -XX:+UseConcMarkSweepGC y vea si su problema desaparece.

+0

El montón es * muy * activo, no de crecimiento lento. Hay una gran cantidad de basura se acumula mientras se ejecuta. Solo después de una hora, los Finalizer ya no se recopilan. – mtraut

+0

Incluso en un montón activo, Java a menudo diferirá la recolección de elementos no utilizados, lo que puede explicar los problemas con el código del finalizador. –

1

Tanto FileInputStream y FileOutputStream tienen mismo comentario en su finalize() métodos:

. . . 
/* 
* 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); 
. . . 

que significa que su Finalizer puede estar esperando corriente para ser lanzado. Lo que significa que, como mencionó anteriormente Joop Eggen, su aplicación puede estar haciendo algo mal al cerrar una de las transmisiones.

+0

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

+0

Volveré a visitar esto una vez más. Pero no veo cómo esto puede explicar la inanición de la finalización. Recuerde, invoque runFinalization para limpiar la cola. – mtraut

Cuestiones relacionadas