Cómo encontrar una fuga de memoria Java


142

¿Cómo encuentra una pérdida de memoria en Java (usando, por ejemplo, JHat)? He intentado cargar el volcado del montón en JHat para echar un vistazo básico. Sin embargo, no entiendo cómo se supone que puedo encontrar la referencia raíz ( ref ) o como se llame. Básicamente, puedo decir que hay varios cientos de megabytes de entradas en la tabla hash ([java.util.HashMap $ Entry o algo así), pero los mapas se usan por todas partes ... ¿Hay alguna forma de buscar mapas grandes? , o tal vez encontrar raíces generales de árboles de objetos grandes?

[Editar] Ok, he leído las respuestas hasta ahora, pero digamos que soy un bastardo barato (lo que significa que estoy más interesado en aprender a usar JHat que pagar por JProfiler). Además, JHat siempre está disponible ya que es parte del JDK. A menos que, por supuesto, JHat no tenga otra forma que la fuerza bruta, pero no puedo creer que ese sea el caso.

Además, no creo que pueda modificar (agregar el registro de todos los tamaños de mapas) y ejecutarlo durante el tiempo suficiente para que note la fuga.


Este es otro "voto" para JProfiler. Funciona bastante bien para el análisis de montón, tiene una interfaz de usuario decente y funciona bastante bien. Como dice McKenzieG1, $ 500 es más barato que la cantidad de tiempo que de lo contrario se quemaría buscando la fuente de estas fugas. En cuanto al precio de las herramientas, no está mal.
joev

Respuestas:


126

Utilizo el siguiente enfoque para encontrar pérdidas de memoria en Java. He usado jProfiler con gran éxito, pero creo que cualquier herramienta especializada con capacidades gráficas (las diferencias son más fáciles de analizar en forma gráfica) funcionará.

  1. Inicie la aplicación y espere hasta que llegue al estado "estable", cuando se complete toda la inicialización y la aplicación esté inactiva.
  2. Ejecute la operación sospechosa de producir una pérdida de memoria varias veces para permitir que se produzca cualquier caché, inicialización relacionada con DB.
  3. Ejecute GC y tome una instantánea de memoria.
  4. Ejecute la operación nuevamente. Dependiendo de la complejidad de la operación y los tamaños de los datos que se procesan, la operación puede necesitar ejecutarse de varias a muchas veces.
  5. Ejecute GC y tome una instantánea de memoria.
  6. Ejecute un diff para 2 instantáneas y analícelo.

Básicamente, el análisis debe comenzar desde la mayor diferencia positiva, por ejemplo, por tipos de objeto y encontrar qué causa que esos objetos adicionales se peguen en la memoria.

Para las aplicaciones web que procesan solicitudes en varios subprocesos, el análisis se vuelve más complicado, pero aún así se aplica un enfoque general.

Realicé bastantes proyectos específicamente destinados a reducir la huella de memoria de las aplicaciones y este enfoque general con algunos ajustes y trucos específicos de la aplicación siempre funcionó bien.


77
La mayoría (si no todos) los perfiladores de Java le brindan la opción de invocar GC con un clic de un botón. O puede llamar a System.gc () desde el lugar apropiado en su código.
Dima Malenko

3
Incluso si llamamos a System.gc (), JVM puede optar por descuidar la llamada. AFAIK esto es específico de JVM. +1 a la respuesta.
Aniket Thakur

44
¿Qué es exactamente una "instantánea de memoria"? ¿Hay algo que me diga el número de cada tipo de objeto que mi código está ejecutando?
Gnomed

2
¿Cómo paso de "empezar desde la mayor diferencia positiva por tipo de objeto" a "encontrar qué causa que esos objetos adicionales se peguen en la memoria"? Veo cosas muy generales como int [], Object [], String, etc. ¿Cómo encuentro de dónde vienen?
Vituel

48

Interrogador aquí, debo decir que obtener una herramienta que no demore 5 minutos en responder cualquier clic hace que sea mucho más fácil encontrar posibles pérdidas de memoria.

Dado que la gente sugiere varias herramientas (solo probé visual wm desde que lo obtuve en la prueba JDK y JProbe), aunque debería sugerir una herramienta de código abierto / libre construida en la plataforma Eclipse, el Analizador de Memoria (a veces referido como la memoria SAP analizador) disponible en http://www.eclipse.org/mat/ .

Lo que es realmente genial de esta herramienta es que indexó el volcado del montón cuando lo abrí por primera vez, lo que le permitió mostrar datos como el montón retenido sin esperar 5 minutos para cada objeto (casi todas las operaciones fueron toneladas más rápidas que las otras herramientas que probé) .

Cuando abre el volcado, la primera pantalla le muestra un gráfico circular con los objetos más grandes (contando el montón retenido) y uno puede navegar rápidamente hacia los objetos que son demasiado grandes para su comodidad. También tiene un hallazgo de posibles sospechosos de fugas que puedo recordar, pero como la navegación fue suficiente para mí, realmente no me metí en eso.


1
Vale la pena señalar: aparentemente en Java 5 y superior, el HeapDumpOnCtrlBreakparámetro VM no está disponible . La solución que he encontrado (hasta ahora, aún buscando) es usar JMap para volcar el .hprofarchivo, que luego puse en Eclipse y uso MAT para examinar.
Ben

1
Con respecto a la obtención del volcado de almacenamiento dinámico, la mayoría de los perfiladores (incluido JVisualVM) incluyen la opción de volcar tanto el almacenamiento dinámico como los subprocesos en un archivo.
bbaja42

13

Una herramienta es de gran ayuda.

Sin embargo, hay momentos en los que no puede usar una herramienta: el volcado de almacenamiento dinámico es tan grande que bloquea la herramienta, está tratando de solucionar problemas de una máquina en algún entorno de producción al que solo tiene acceso de shell, etc.

En ese caso, es útil conocer el archivo de volcado hprof.

Busque SITIOS COMIENZAN. Esto le muestra qué objetos están usando más memoria. Pero los objetos no se agrupan únicamente por tipo: cada entrada también incluye una identificación de "rastreo". Luego puede buscar ese "TRACE nnnn" para ver los pocos fotogramas superiores de la pila donde se asignó el objeto. A menudo, una vez que veo dónde está asignado el objeto, encuentro un error y termino. Además, tenga en cuenta que puede controlar cuántos fotogramas se graban en la pila con las opciones de -Xrunhprof.

Si revisa el sitio de asignación y no ve nada malo, debe comenzar el encadenamiento hacia atrás de algunos de esos objetos vivos a objetos raíz, para encontrar la cadena de referencia inesperada. Aquí es donde una herramienta realmente ayuda, pero puede hacer lo mismo a mano (bueno, con grep). No hay un solo objeto raíz (es decir, un objeto no sujeto a recolección de basura). Los subprocesos, las clases y los marcos de pila actúan como objetos raíz, y cualquier cosa a la que hagan referencia con fuerza no es coleccionable.

Para hacer el encadenamiento, busque en la sección HEAP DUMP las entradas con la identificación de seguimiento incorrecta. Esto lo llevará a una entrada OBJ o ARR, que muestra un identificador de objeto único en hexadecimal. Busque todas las apariciones de esa identificación para encontrar quién tiene una fuerte referencia al objeto. Siga cada uno de esos caminos hacia atrás a medida que se ramifican hasta que descubra dónde está la fuga. ¿Ves por qué una herramienta es tan útil?

Los miembros estáticos son reincidentes por pérdidas de memoria. De hecho, incluso sin una herramienta, valdría la pena pasar unos minutos buscando miembros estáticos en el mapa a través de su código. ¿Puede crecer un mapa? ¿Algo alguna vez limpia sus entradas?


“El volcado de almacenamiento dinámico es tan grande que bloquea la herramienta”; después lo revisé, jhaty MATaparentemente trato de cargar todo el volcado de almacenamiento dinámico en la memoria, por lo que normalmente se bloquea con OutOfMemoryErrorvolcados grandes (es decir, de las aplicaciones que más necesitaban análisis de almacenamiento dinámico). ) NetBeans Profiler parece usar un algoritmo diferente para indexar referencias que puede ser lento en volcados grandes pero que al menos no consume memoria ilimitada en la herramienta y se bloquea.
Jesse Glick

10

La mayoría de las veces, en las aplicaciones empresariales, el montón de Java dado es mayor que el tamaño ideal de un máximo de 12 a 16 GB. Me ha resultado difícil hacer que el generador de perfiles de NetBeans funcione directamente en estas grandes aplicaciones de Java.

Pero generalmente esto no es necesario. Puede usar la utilidad jmap que viene con jdk para realizar un volcado de almacenamiento dinámico "en vivo", es decir, jmap volcará el almacenamiento dinámico después de ejecutar GC. Realice alguna operación en la aplicación, espere hasta que se complete la operación, luego realice otro volcado de pila "en vivo". Use herramientas como Eclipse MAT para cargar los heapdumps, ordenar en el histograma, ver qué objetos han aumentado o cuáles son los más altos, esto daría una pista.

su  proceeuser
/bin/jmap -dump:live,format=b,file=/tmp/2930javaheap.hrpof 2930(pid of process)

Solo hay un problema con este enfoque; Los grandes volcados de almacenamiento dinámico, incluso con la opción en vivo, pueden ser demasiado grandes para transferir a la vuelta de desarrollo, y pueden necesitar una máquina con suficiente memoria / RAM para abrir.

Ahí es donde aparece el histograma de clase. Puede volcar un histograma de clase en vivo con la herramienta jmap. Esto le dará solo el histograma de clase de uso de memoria. Básicamente no tendrá la información para encadenar la referencia. Por ejemplo, puede poner una matriz de caracteres en la parte superior. Y la clase String en algún lugar debajo. Tienes que dibujar la conexión tú mismo.

jdk/jdk1.6.0_38/bin/jmap -histo:live 60030 > /tmp/60030istolive1330.txt

En lugar de realizar dos volcados de almacenamiento dinámico, tome dos histogramas de clase, como se describe anteriormente; Luego compare los histogramas de clase y vea las clases que están aumentando. Vea si puede relacionar las clases Java con sus clases de aplicación. Esto le dará una muy buena pista. Aquí hay un script de pitones que puede ayudarlo a comparar dos volcados de histograma jmap. histogramparser.py

Finalmente, herramientas como JConolse y VisualVm son esenciales para ver el crecimiento de la memoria con el tiempo y ver si hay una pérdida de memoria. Finalmente, a veces su problema puede no ser una pérdida de memoria, sino un uso elevado de la memoria. Para esto, active el registro de GC; use un GC de compactación más avanzado y nuevo como G1GC; y puede usar herramientas jdk como jstat para ver el comportamiento de GC en vivo

jstat -gccause pid <optional time interval>

Otras referencias a google para -jhat, jmap, GC completo, asignación enorme, G1GC


1
agregó una publicación de blog con más detalles aquí - alexpunnen.blogspot.in/2015/06/…
Alex Punnen

5

Existen herramientas que deberían ayudarlo a encontrar su fuga, como JProbe, YourKit, AD4J o JRockit Mission Control. El último es el que personalmente conozco mejor. Cualquier buena herramienta debería permitirle profundizar a un nivel en el que pueda identificar fácilmente qué fugas y dónde se asignan los objetos con fugas.

El uso de HashTables, Hashmaps o similar es una de las pocas formas en que puede perder memoria en Java. Si tuviera que encontrar la fuga a mano, imprimiría periódicamente el tamaño de mis HashMaps, y desde allí encontraría el que agrego elementos y me olvidaré de eliminarlos.


4

Bueno, siempre existe la solución de baja tecnología de agregar el registro del tamaño de sus mapas cuando los modifica, luego busque en los registros los mapas que crecen más allá de un tamaño razonable.


1

NetBeans tiene un generador de perfiles incorporado.


0

Realmente necesita usar un generador de perfiles de memoria que rastree las asignaciones. Eche un vistazo a JProfiler : su función "heap walker" es excelente, y tienen integración con todos los IDE principales de Java. No es gratis, pero tampoco es tan costoso ($ 499 por una sola licencia): gastará $ 500 en tiempo luchando rápidamente para encontrar una fuga con herramientas menos sofisticadas.


0

Puede averiguarlo midiendo el tamaño de uso de la memoria después de llamar al recolector de basura varias veces:

Runtime runtime = Runtime.getRuntime();

while(true) {
    ...
    if(System.currentTimeMillis() % 4000 == 0){
        System.gc();
        float usage = (float) (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
        System.out.println("Used memory: " + usage + "Mb");
    }

}

Si los números de salida eran iguales, no hay pérdida de memoria en su aplicación, pero si vio una diferencia entre los números de uso de memoria (números crecientes), hay una pérdida de memoria en su proyecto. Por ejemplo:

Used memory: 14.603279Mb
Used memory: 14.737213Mb
Used memory: 14.772224Mb
Used memory: 14.802681Mb
Used memory: 14.840599Mb
Used memory: 14.900841Mb
Used memory: 14.942261Mb
Used memory: 14.976143Mb

Tenga en cuenta que a veces lleva algún tiempo liberar memoria mediante algunas acciones, como secuencias y sockets. No debe juzgar por las primeras salidas, debe probarlo en un período de tiempo específico.


0

Echa un vistazo a este reparto de pantalla sobre la búsqueda de pérdidas de memoria con JProfiler. Es una explicación visual de @Dima Malenko Answer.

Nota: Aunque JProfiler no es un programa gratuito, la versión de prueba puede manejar la situación actual.


0

Como la mayoría de nosotros ya usamos Eclipse para escribir código, ¿por qué no usar la herramienta Memory Analyzer Tool (MAT) en Eclipse? Funciona muy bien

El Eclipse MAT es un conjunto de plug-ins para el IDE Eclipse que proporciona herramientas para analizar heap dumpsdesde la aplicación Java y para identificar memory problemsen la aplicación.

Esto ayuda al desarrollador a encontrar pérdidas de memoria con las siguientes características

  1. Adquisición de una instantánea de memoria (volcado del montón)
  2. Histograma
  3. Montón retenido
  4. Árbol dominador
  5. Explorando caminos hacia las raíces de GC
  6. Inspector
  7. Antipatrones comunes de memoria
  8. Lenguaje de consulta de objetos

ingrese la descripción de la imagen aquí

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.