java memory-leaks jni

Larga vida Java WeakReferences



memory-leaks jni (7)

@iirekm No: WeakReferences son ''más débiles'' que SoftReferences, lo que significa que una WeakReference siempre será recogida de basura antes de una SoftReference.

Más información en esta publicación: Comprender las clases de referencia de Java: SoftReference, WeakReference y PhantomReference

Edición: (después de leer los comentarios) Sí, seguramente, las referencias débiles son ''más débiles'' que las referencias rápidas, typo. : S

Aquí hay algunos casos de uso para arrojar más luz sobre el tema:

  • SoftReference : caché en memoria (el objeto permanece vivo hasta que la máquina virtual considera que no hay suficientes memoria de pila)
  • Referencia débil : escuchas que se borran automáticamente (el objeto debe borrarse en el siguiente ciclo del GC después de que se considere que se puede alcanzar de forma débil )
  • Referencia fantasma : evitar errores de falta de memoria cuando se manejan objetos inusualmente grandes (cuando se programa en la cola de referencia, sabemos que el objeto del host se debe borrar, es seguro asignar otro objeto grande). Piense en esto como una alternativa de finalización (), sin la capacidad de devolver a la vida los objetos muertos (como podría potencialmente finalizar)

Dicho esto, nada impide que la VM (corríjame si me equivoco) permita que los objetos que se pueden alcanzar con facilidad permanezcan vivos mientras no se quede sin memoria (como en el caso original del autor).

Este es el mejor recurso que pude encontrar sobre el tema: http://www.pawlan.com/monica/articles/refobjs/

Edit 2: Se agregó "to be" delante de borrado en PhantomRef

Actualmente estoy tratando de diagnosticar una pérdida de memoria lenta en mi aplicación. Los hechos que tengo hasta ahora son los siguientes.

  • Tengo un volcado de pila de una ejecución de 4 días de la aplicación.
  • Este volcado de almacenamiento contiene objetos de ~ 800 WeakReference que apuntan a objetos (todos del mismo tipo, que llamaré Foo para los propósitos de esta pregunta) y que retienen 40 MB de memoria.
  • La herramienta de análisis de memoria de Eclipse muestra que a cada uno de los objetos Foo a los que se hace referencia en estas referencias débiles no se hace referencia a ningún otro objeto. Mi expectativa es que esto debería hacer que estos objetos Foo sean débilmente accesibles y, por lo tanto, deberían recolectarse en el próximo CG.
  • Cada uno de estos objetos Foo tiene una marca de tiempo que muestra que se asignaron en el transcurso de la ejecución de 4 días. También tengo registros durante este tiempo que confirman que la recolección de basura estaba ocurriendo.
  • Mi aplicación está creando una gran cantidad de objetos Foo y solo una fracción muy pequeña de ellos está terminando en este estado dentro del volcado de almacenamiento dinámico. Esto me sugiere que la causa raíz es algún tipo de condición de raza.
  • Mi aplicación utiliza JNI para llamar a una biblioteca nativa. El código JNI llama a NewGlobalRef 4 veces durante el inicio del día para obtener referencias a las clases de Java que utiliza.

¿Qué podría hacer que estas clases de Foo no se recopilen a pesar de que WeakReferences (según la herramienta Eclipse Memory Analyzer Tool) hace referencia a ellas?

EDIT1:

@mindas La WeakReference que estoy usando es equivalente al siguiente código de ejemplo.

public class FooWeakRef extends WeakReference<Foo> { public long longA; public long longB; public String stringA; public FooWeakRef(Foo xiObject, ReferenceQueue<Foo> xiQueue) { super(xiObject, xiQueue); } }

Foo no tiene un finalizador y cualquier finalizador no sería una consideración siempre y cuando los WeakRefs no hayan sido eliminados. Un objeto no es finalizable cuando está débilmente accesible. Vea esta página para más detalles .

@kasten Las referencias débiles se borran antes de que el objeto se pueda finalizar. Mi montón de descarga muestra que esto no ha sucedido.

@jarnbjo me refiero al Javadoc WeakReference:

"Supongamos que el recolector de basura determina en un cierto punto en el tiempo que un objeto es débilmente accesible. En ese momento borrará atómicamente todas las referencias débiles a ese objeto y todas las referencias débiles a cualquier otro objeto débilmente accesible desde el cual ese objeto sea accesible. A través de una cadena de referencias fuertes y suaves ".

Esto me sugiere que el GC debería estar detectando el hecho de que mis objetos Foo son "Débilmente accesibles" y "En ese momento" borrando las referencias débiles.

Editar 2

@j flemm: sé que 40mb no parece mucho, pero me preocupa que 40mb en 4 días signifique 4000mb en 100 días. Todos los documentos que he leído sugieren que los objetos que son de difícil acceso no deben permanecer durante varios días. Por lo tanto, estoy interesado en cualquier otra explicación acerca de cómo se puede hacer una referencia fuerte a un objeto sin que la referencia aparezca en un volcado de almacenamiento dinámico.

Voy a intentar asignar algunos objetos grandes cuando algunos de estos objetos Foo colgantes están presentes y ver si la JVM los recopila. Sin embargo, esta prueba tardará un par de días en configurarse y completarse.

EDITAR 3

@jarnbjo: entiendo que no tengo ninguna garantía sobre cuándo el JDK notará que un objeto es débilmente accesible. Sin embargo, esperaría que una aplicación con carga pesada durante 4 días brindara suficientes oportunidades para que el GC note que mis objetos son poco accesibles. Después de 4 días sospecho fuertemente que los objetos remanentes débilmente remanentes se han filtrado de alguna manera.

EDITAR 4

@j flemm - ¡Eso es realmente interesante! Solo para aclarar, ¿estás diciendo que GC está ocurriendo en tu aplicación y no está eliminando refs Soft / Weak? ¿Me puede dar más detalles sobre qué configuración de JVM + GC está usando? Mi aplicación está utilizando una barra de memoria al 80% del montón para activar GC. Estaba asumiendo que cualquier GC de la vieja generación eliminaría las referencias débiles. ¿Está sugiriendo que un GC solo recopila referencias débiles una vez que el uso de la memoria está por encima de un umbral más alto? ¿Es este límite superior configurable?

Editar 5

@j flemm: su comentario sobre la eliminación de WeakRefs antes de SoftRefs es coherente con el Javadoc que dice: SoftRef: "Supongamos que el recolector de basura determina en un determinado momento que un objeto es fácilmente accesible. En ese momento puede elegir borrar Atómicamente, todas las referencias blandas a ese objeto y todas las referencias blandas a cualquier otro objeto de fácil acceso desde el cual se pueda acceder a ese objeto a través de una cadena de referencias fuertes. Al mismo tiempo o en algún momento posterior, se pondrán en cola las referencias blandas recién borradas que están registrados con colas de referencia ".

WeakRef: "Supongamos que el recolector de basura determina en un determinado momento que un objeto es débilmente accesible. En ese momento borrará atómicamente todas las referencias débiles a ese objeto y todas las referencias débiles a cualquier otro objeto de difícil acceso desde el cual ese objeto es accesible a través de una cadena de referencias fuertes y suaves. Al mismo tiempo, declarará que todos los objetos que antes se encontraban a los que se tiene poca capacidad para ser finalizables. Al mismo tiempo, o en algún momento posterior, encolará aquellas referencias débiles recién aclaradas que son registrado con colas de referencia ".

Para mayor claridad, ¿está diciendo que el recolector de basura se ejecuta cuando su aplicación tiene más del 50% de memoria libre y en este caso no borra WeakRefs? ¿Por qué funcionaría el GC cuando su aplicación tiene más del 50% de memoria libre? Creo que su aplicación probablemente está generando una cantidad muy baja de basura y cuando el recolector se ejecuta, está eliminando WeakRefs pero no SoftRefs.

Editar 6

@j flemm - La otra explicación posible para el comportamiento de su aplicación es que se está recolectando el gen joven pero que sus referencias débiles y blandas están todas en el gen antiguo y solo se borran cuando se está recolectando el gen anterior. Para mi aplicación tengo estadísticas que muestran que se está recopilando la generación anterior, lo que debería significar que WeakRefs se borra.

Editar 7

Estoy empezando una recompensa por esta pregunta. Estoy buscando explicaciones plausibles de cómo WeakRefs podría no ser borrado mientras GC está sucediendo. Si la respuesta es que esto es imposible, lo ideal sería apuntar a los bits apropiados de OpenJDK, que muestran que WeakRefs se está eliminando tan pronto como se determina que un objeto es débilmente accesible y que la accesibilidad débil se resuelve cada vez que se ejecuta GC.


Es posible que desee comprobar si se ha filtrado el problema del cargador de clases. Más sobre este tema que puedes encontrar en this entrada de blog


Finalmente, me puse a revisar el código fuente de Hotspot JVM y encontré el siguiente código.

En referenceProcessor.cpp:

void ReferenceProcessor::process_discovered_references( BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor) { NOT_PRODUCT(verify_ok_to_handle_reflists()); assert(!enqueuing_is_done(), "If here enqueuing should not be complete"); // Stop treating discovered references specially. disable_discovery(); bool trace_time = PrintGCDetails && PrintReferenceGC; // Soft references { TraceTime tt("SoftReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true, is_alive, keep_alive, complete_gc, task_executor); } update_soft_ref_master_clock(); // Weak references { TraceTime tt("WeakReference", trace_time, false, gclog_or_tty); process_discovered_reflist(_discoveredWeakRefs, NULL, true, is_alive, keep_alive, complete_gc, task_executor); }

La función process_discovered_reflist tiene la siguiente firma:

void ReferenceProcessor::process_discovered_reflist( DiscoveredList refs_lists[], ReferencePolicy* policy, bool clear_referent, BoolObjectClosure* is_alive, OopClosure* keep_alive, VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor)

Esto muestra que WPrecessor :: process_discovered_references está limpiando incondicionalmente WeakRefs.

La búsqueda en el código de punto de acceso para la referencia_de_proceso_descubrimiento muestra que el recopilador de CMS (que es lo que estoy usando) llama a este método desde la siguiente pila de llamadas.

CMSCollector::refProcessingWork CMSCollector::checkpointRootsFinalWork CMSCollector::checkpointRootsFinal

Esta pila de llamadas parece que se invoca cada vez que se ejecuta una colección de CMS.

Suponiendo que esto sea cierto, la única explicación para un objeto de referencia débil de larga vida sería un error JVM sutil o si el GC no se hubiera ejecutado.


No estoy familiarizado con Java, pero es posible que esté utilizando un recolector de basura generacional , que mantendrá sus objetos Foo y FooWeakRef solos (no recopilados) siempre que

  • pasaron en una generación mayor
  • Hay suficiente memoria para asignar nuevos objetos en las generaciones más jóvenes.

¿El registro que indica que se realizó la recolección de basura discrimina entre las colecciones principales y secundarias?


Para los no creyentes que afirman que las referencias débiles se borran antes que las referencias blandas:

import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.Map; public class Test { /** * @param args */ public static void main(String[] args) { ReferenceQueue<Object> q = new ReferenceQueue<Object>(); Map<Reference<?>, String> referenceToId = new HashMap<Reference<?>, String>(); for(int i=0; i<100; ++i) { Object obj = new byte [10*1024*1024]; // 10M SoftReference<Object> sr = new SoftReference<Object>(obj, q); referenceToId.put(sr, "soft:"+i); WeakReference<Object> wr = new WeakReference<Object>(obj, q); referenceToId.put(wr, "weak:"+i); for(;;){ Reference<?> ref = q.poll(); if(ref == null) { break; } System.out.println("cleared reference " + referenceToId.get(ref) + ", value=" + ref.get()); } } } }

Si lo ejecuta con -client o -server, verá que las referencias blandas siempre se borran antes que las débiles, lo que también concuerda con Javadoc: download.oracle.com/javase/1.4.2/docs/api/java/lang/ref/…

Normalmente, las referencias débiles / débiles se usan en conexión con Mapas para hacer tipos de cachés. Si las teclas en su Mapa se comparan con el operador ==, (o unoverriden .equals from Object), entonces es mejor usar el Mapa que opera con las teclas de Referencia Soft (por ejemplo, de Apache Commons): cuando el objeto "desaparece", ningún otro objeto ser igual en el sentido ''=='' al anterior. Si las claves de su Mapa se comparan con el operador .equals () avanzado, como String o Date, muchos otros objetos pueden coincidir con el que "desaparece", por lo que es mejor usar el WeakHashMap estándar.


Pruebe SoftReference en su lugar. Javadoc dice: Se garantiza que todas las referencias blandas a objetos de fácil acceso se han borrado antes de que la máquina virtual lance un OutOfMemoryError.

WeakReference no tiene tales garantías, lo que las hace más adecuadas para el almacenamiento en caché, pero a veces SoftReferences es mejor.


WeakReference aclarar cuál es el vínculo entre Foo y WeakReference . El caso

class Wrapper<T> extends WeakReference<T> { private final T referent; public Wrapper(T referent) { super(t); this.referent = referent; } }

es muy diferente de solo

class Wrapper<T> extends WeakReferece<T> { public Wrapper(T referent) { super(t); } }

o su versión en línea, WeakReference<Foo> wr = new WeakReference<Foo>(foo) .

Entonces asumo que su caso no es como lo describí en mi primer fragmento de código.

Como ha dicho que está trabajando con JNI, es posible que desee verificar si tiene algún finalizador inseguro. Cada finalizador debería tener finally bloqueo de las llamadas super.finalize() y es fácil de deslizar.

Probablemente necesite contarnos más sobre la naturaleza de sus objetos para ofrecer mejores ideas.