java - ¿Cómo puedo descubrir qué es lo que está aferrado a los objetos no liberados?
java profiler (11)
Uno de nuestros programas a veces obtiene un error OutOfMemory
en la máquina de un usuario, pero por supuesto no cuando lo estoy probando. Lo acabo de ejecutar con JProfiler (en una licencia de evaluación de 10 días porque nunca lo había usado antes), y filtrando en nuestro prefijo de código, el mayor fragmento tanto en tamaño total como en cantidad de instancias es más de 8000 instancias de una clase simple particular .
Hice clic en el botón "Recolectar basura" en JProfiler, y la mayoría de las instancias de otras clases desaparecieron, pero no estas en particular. Ejecuté la prueba nuevamente, todavía en la misma instancia, y creé más de 4000 instancias de la clase, pero cuando hice clic en "Recolectar basura", esas desaparecieron dejando las más de 8000 originales.
Estas instancias quedan atrapadas en varias Colecciones en varias etapas. Supongo que el hecho de que no sean basura recolectada debe significar que algo está reteniendo una referencia a una de las colecciones, por lo que se mantiene en una referencia a los objetos.
¿Alguna sugerencia de cómo puedo descubrir qué contiene la referencia? Estoy buscando sugerencias de qué buscar en el código, así como las formas de descubrir esto en JProfiler si las hay.
Acabo de leer un artículo sobre esto, pero lamento no recordar dónde. Creo que podría haber estado en el libro "Effective Java". Si encuentro la referencia, actualizaré mi respuesta.
Las dos lecciones importantes que delineó son:
1) Los métodos finales le dicen al gc qué hacer cuando descarta el objeto, pero no le pide que lo haga, ni existe una forma de exigir que lo haga.
2) El equivalente moderno de la "fuga de memoria" en entornos de memoria no administrados, son las referencias olvidadas. Si no establece que todas las referencias a un objeto sean nulas cuando haya terminado, el objeto nunca se eliminará. Esto es más importante cuando implementa su propio tipo de Colección, o su propio contenedor que administra una Colección. Si tiene un grupo, una pila o una cola, y no establece que el depósito sea nulo cuando "elimina" un objeto de la colección, el depósito en el que se encontraba el objeto mantendrá vivo ese objeto hasta que ese depósito se configure como referirse a otro objeto
Descargo de responsabilidad: Sé que otras respuestas mencionaron esto, pero estoy tratando de ofrecer más detalles.
Esté atento a los contenedores estáticos. Cualquier objeto en un contenedor estático permanecerá mientras se cargue la clase.
Editar: eliminó la observación incorrecta en WeakReference.
He utilizado el perfilador Yourkit Java ( http://www.yourkit.com ) para optimizaciones de rendimiento en Java 1.5. Tiene una sección sobre cómo trabajar en fugas de memoria. Lo encuentro útil.
http://www.yourkit.com/docs/75/help/performance_problems/memory_leaks/index.jsp
Puede obtener una evaluación de 15 días: http://www.yourkit.com/download/yjp-7.5.7.exe
BR,
~ A
Me gustaría ver las colecciones (especialmente las estáticas) en sus clases (HashMaps son un buen lugar para comenzar). Tome este código por ejemplo:
Map<String, Object> map = new HashMap<String, Object>(); // 1 Object
String name = "test"; // 2 Objects
Object o = new Object(); // 3 Objects
map.put(name, o); // 3 Objects, 2 of which have 2 references to them
o = null; // The objects are still being
name = null; // referenced by the HashMap and won''t be GC''d
System.gc(); // Nothing is deleted.
Object test = map.get("test"); // Returns o
test = null;
map.remove("test"); // Now we''re down to just the HashMap in memory
// o, name and test can all be GC''d
Siempre que HashMap o alguna otra colección tenga una referencia a ese objeto, no se recolectará basura.
No hay una bala de plata allí, tienes que usar el perfilador para identificar las colecciones que contienen esos objetos innecesarios y encontrar el lugar en el código donde deberían haber sido eliminados. Como dijo JesperE, las colecciones estáticas son el primer lugar para mirar.
Pruebe Eclipse Memory Analyzer. Le mostrará para cada objeto cómo está conectado a una raíz de GC: un objeto que no es basura porque la JVM lo tiene.
Consulte http://dev.eclipse.org/blogs/memoryanalyzer/2008/05/27/automated-heap-dump-analysis-finding-memory-leaks-with-one-click/ para obtener más información sobre cómo funciona Eclipse MAT.
Si obtiene errores OOM en un lenguaje basura, generalmente significa que el recopilador no tiene memoria. ¿Tal vez sus objetos contienen recursos que no son de Java? si es así, entonces deberían tener algún tipo de método "cerrado" para asegurarse de que el recurso se libere, incluso si el objeto Java no se recopila lo suficientemente pronto.
Un candidato obvio es los objetos con finalizadores. Pueden quedarse mientras se llama su método de finalización. Necesitan ser recolectados, luego finalizados (generalmente con solo un hilo finalizador) y luego recolectados nuevamente.
También tenga en cuenta que puede obtener un OOME porque el gc no pudo recopilar suficiente memoria, a pesar de que en realidad haya suficiente para que se cree la solicitud del objeto. De lo contrario, el rendimiento se reduciría a la tierra.
Volcar el montón e inspeccionarlo.
Estoy seguro de que hay más de una forma de hacerlo, pero aquí hay uno simple. Esta descripción es para MS Windows, pero se pueden tomar pasos similares en otros sistemas operativos.
- Instala el JDK si aún no lo tienes. Viene con un montón de herramientas ordenadas.
- Comience la aplicación.
- Abre el administrador de tareas y encuentra el ID de proceso (PID) para java.exe (o el ejecutable que estés usando). Si los PID no se muestran de manera predeterminada, use Ver> Seleccionar columnas ... para agregarlos.
- Vuelca el montón usando jmap .
- Inicie el servidor jhat en el archivo que generó y abra su navegador en http: // localhost: 7000 (el puerto predeterminado es 7000). Ahora puede navegar por el tipo que le interesa e información como el número de instancias, lo que tiene referencias sobre ellos, etcétera.
Aquí hay un ejemplo:
C:/dump>jmap -dump:format=b,file=heap.bin 3552
C:/dump>jhat heap.bin
Reading from heap.bin...
Dump file created Tue Sep 30 19:46:23 BST 2008
Snapshot read, resolving...
Resolving 35484 objects...
Chasing references, expect 7 dots.......
Eliminating duplicate references.......
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.
Para interpretar esto, es útil comprender algunas de las nomenclaturas de tipos de matrices que utiliza Java, como conocer esa clase [Ljava.lang.Object; realmente significa un objeto de tipo Object [] .
Las colecciones ya fueron mencionadas. Otra ubicación difícil de encontrar es si usa varios ClassLoaders, ya que es posible que el cargador de clases antiguo no se pueda recolectar como basura hasta que todas las referencias hayan desaparecido.
También verifique la estática: estos son desagradables. Los marcos de registro pueden mantener las cosas abiertas, lo que puede mantener las referencias en los apéndices personalizados.
¿Resolviste el problema?
Algunas sugerencias:
- Mapas ilimitados utilizados como cachés, especialmente cuando estático
- ThreadLocal en aplicaciones de servidor, porque los hilos generalmente no se mueren, por lo que no se libera el ThreadLocal
- Interning strings (Strings.intern ()), que da como resultado un montón de Strings en PermSpace