collector - Método para encontrar pérdida de memoria en grandes volcados de almacenamiento dinámico de Java
eclipse memory analyzer download (7)
Tengo que encontrar una pérdida de memoria en una aplicación Java. Tengo algo de experiencia con esto, pero me gustaría un consejo sobre una metodología / estrategia para esto. Cualquier referencia y consejo es bienvenido.
Sobre nuestra situación:
- Los vertederos son más grandes que 1 GB
- Tenemos montones de vertederos a partir de 5 ocasiones.
- No tenemos ningún caso de prueba para provocar esto. Solo ocurre en el entorno de prueba del sistema (masivo) después de al menos una semana de uso.
- El sistema se basa en un marco heredado desarrollado internamente con tantos defectos de diseño que es imposible contarlos todos.
- Nadie entiende el marco en profundidad. Se ha transferido a un chico en India que apenas se mantiene al día con los correos electrónicos de respuesta.
- Hemos hecho volcados de instantáneas en pilas a lo largo del tiempo y hemos llegado a la conclusión de que no hay un solo componente que aumente con el tiempo. Es todo lo que crece lentamente.
- Lo anterior nos indica la dirección en la que son los sistemas ORM de cosecha propia los que aumentan su uso sin límites. (¡¿Este sistema mapea objetos a archivos ?! Entonces no es realmente un ORM)
Pregunta: ¿Cuál es la metodología que lo ayudó a tener éxito en la búsqueda de fugas en una aplicación de escala empresarial?
¿Se puede acelerar el tiempo? es decir, ¿puede escribir un cliente de prueba ficticio que lo obligue a realizar llamadas / solicitudes, etc., de una semana de duración, en unos pocos minutos u horas? Estos son tus mejores amigos y si no tienes uno, escribe uno.
Usamos Netbeans hace un tiempo para analizar volcados de pila. Puede ser un poco lento pero fue efectivo. Eclipse simplemente se estrelló y las herramientas de 32 bits de Windows también lo hicieron.
Si tiene acceso a un sistema de 64 bits o a un sistema Linux con 3 GB o más, le resultará más fácil analizar los volcados de pila.
¿Tiene acceso a los registros de cambios e informes de incidentes? Las empresas a gran escala normalmente tendrán equipos de gestión de cambio y gestión de incidentes, y esto puede ser útil para rastrear cuándo comenzaron los problemas.
¿Cuándo empezó a ir mal? Habla con la gente y trata de obtener algo de historia. Puedes hacer que alguien diga: "Sí, fue después de que arreglaran XYZ en el parche 6.43 que sucedió algo extraño".
Echa un vistazo a Eclipse Memory Analyzer . Es una gran herramienta (y autocontenida, no requiere que se instale Eclipse) que 1) puede abrir montones muy grandes muy rápido y 2) tiene algunas herramientas de detección automática bastante buenas. Este último no es perfecto, pero EMA proporciona muchas formas realmente agradables de navegar y consultar los objetos en el volcado para encontrar posibles fugas.
Lo he usado en el pasado para ayudar a cazar fugas sospechosas.
Es casi imposible sin una cierta comprensión del código subyacente. Si comprende el código subyacente, entonces puede clasificar mejor el trigo de la gran cantidad de los miles de millones de información que está obteniendo en sus vertederos.
Además, no puede saber si algo es una fuga o no sin saber por qué la clase está ahí en primer lugar.
Acabo de pasar las últimas semanas haciendo exactamente esto, y utilicé un proceso iterativo.
Primero, encontré a los perfiladores de pilas básicamente inútiles. No pueden analizar los enormes montones de manera eficiente.
Más bien, me basé casi exclusivamente en los histogramas jmap .
Me imagino que estás familiarizado con estos, pero para aquellos que no:
jmap -histo:live <pid> > dump.out
crea un histograma del montón vivo. En pocas palabras, te dice los nombres de las clases y cuántas instancias de cada clase hay en el montón.
Estaba abandonando el montón regularmente, cada 5 minutos, 24 horas al día. Eso puede ser demasiado granular para ti, pero la esencia es la misma.
Hice varios análisis diferentes sobre estos datos.
Escribí un guión para tomar dos histogramas y descartar la diferencia entre ellos. Entonces, si java.lang.String tenía 10 en el primer volcado y 15 en el segundo, mi script escupiría "5 java.lang.String", diciéndome que subió en 5. Si se hubiera caído, el El número sería negativo.
Luego tomaría varias de estas diferencias, eliminaría todas las clases que bajaban de una carrera a otra y tomaría una unión del resultado. Al final, tendría una lista de clases que creció continuamente durante un período de tiempo específico. Obviamente, estos son los principales candidatos para las clases de fugas.
Sin embargo, algunas clases tienen algunas conservadas, mientras que otras son GC''d. Estas clases podrían subir y bajar fácilmente en general, y aun así tener fugas. Por lo tanto, podrían caer de la categoría de clases "siempre ascendentes".
Para encontrarlos, convertí los datos en una serie de tiempo y los cargué en una base de datos, específicamente Postgres. Postgres es útil porque ofrece funciones estadísticas agregadas , por lo que puede hacer un análisis de regresión lineal simple en los datos y encontrar clases que aumentan, incluso si no siempre están en la cima de los gráficos. Utilicé la función regr_slope, buscando clases con una pendiente positiva.
Encontré este proceso muy exitoso y realmente eficiente. Los archivos de histogramas no son increíblemente grandes, y fue fácil descargarlos de los hosts. No eran muy caros de ejecutar en el sistema de producción (obligan a un GC grande y pueden bloquear la VM por un momento). Estaba ejecutando esto en un sistema con un montón de Java 2G.
Ahora, todo lo que puede hacer es identificar clases potencialmente con fugas.
Aquí es donde se entiende cómo se usan las clases y si deben o no ser parte de ellas.
Por ejemplo, puede encontrar que tiene muchas clases Map.Entry, o alguna otra clase de sistema.
A menos que simplemente esté guardando en caché String, el hecho es que estas clases de sistema, mientras que quizás los "delincuentes", no son el "problema". Si está guardando en caché alguna clase de aplicación, ESA clase es un mejor indicador de dónde se encuentra su problema. Si no almacena en caché com.app.yourbean, no tendrá el Map.Entry asociado asociado a él.
Una vez que tenga algunas clases, puede comenzar a rastrear la base del código en busca de instancias y referencias. Ya que tiene su propia capa ORM (para bien o para mal), al menos puede mirar fácilmente el código fuente. Si su ORM almacena en caché, es probable que almacene en caché las clases de ORM que encierran sus clases de aplicación.
Finalmente, otra cosa que puede hacer, es que una vez que conozca las clases, puede iniciar una instancia local del servidor, con un montón mucho más pequeño y un conjunto de datos más pequeño, y usar uno de los perfiladores en contra.
En este caso, puede hacer una prueba unitaria que afecta solo a 1 (o un pequeño número) de las cosas que cree que pueden estar goteando. Por ejemplo, podría iniciar el servidor, ejecutar un histograma, realizar una sola acción y volver a ejecutar el histograma. Tu clase de fugas debería haber aumentado en 1 (o cualquiera que sea tu unidad de trabajo).
Un generador de perfiles puede ayudarlo a rastrear a los propietarios de esa clase "ahora filtrada".
Pero, al final, tendrá que comprender su base de código para comprender mejor qué es una fuga y qué no, y por qué existe un objeto en el montón, y mucho menos por qué se puede retener. como una fuga en su montón.
Esta respuesta se expande sobre la de @Will-Hartung. Solicité el mismo proceso para diagnosticar una de las fugas de memoria y pensé que compartir los detalles ahorraría tiempo a otras personas.
La idea es tener el tiempo de ''gráfico'' de postgres frente al uso de memoria de cada clase, dibujar una línea que resuma el crecimiento e identificar los objetos que están creciendo más rápido:
^
|
s | Legend:
i | * - data point
z | -- - trend
e |
( |
b | *
y | --
t | --
e | * -- *
s | --
) | *-- *
| -- *
| -- *
--------------------------------------->
time
Convierta sus volcados de pila (necesita múltiples) en un formato que sea conveniente para el consumo por postgres desde el formato de volcado de pila:
num #instances #bytes class name
----------------------------------------------
1: 4632416 392305928 [C
2: 6509258 208296256 java.util.HashMap$Node
3: 4615599 110774376 java.lang.String
5: 16856 68812488 [B
6: 278914 67329632 [Ljava.util.HashMap$Node;
7: 1297968 62302464
...
A un archivo csv con la fecha y hora de cada volcado de pila:
2016.09.20 17:33:40,[C,4632416,392305928
2016.09.20 17:33:40,java.util.HashMap$Node,6509258,208296256
2016.09.20 17:33:40,java.lang.String,4615599,110774376
2016.09.20 17:33:40,[B,16856,68812488
...
Utilizando este script:
# Example invocation: convert.heap.hist.to.csv.pl -f heap.2016.09.20.17.33.40.txt -dt "2016.09.20 17:33:40" >> heap.csv
my $file;
my $dt;
GetOptions (
"f=s" => /$file,
"dt=s" => /$dt
) or usage("Error in command line arguments");
open my $fh, ''<'', $file or die $!;
my $last=0;
my $lastRotation=0;
while(not eof($fh)) {
my $line = <$fh>;
$line =~ s//R//g; #remove newlines
# 1: 4442084 369475664 [C
my ($instances,$size,$class) = ($line =~ /^/s*/d+:/s+(/d+)/s+(/d+)/s+([/$/[/w/.]+)/s*$/) ;
if($instances) {
print "$dt,$class,$instances,$size/n";
}
}
close($fh);
Crear una tabla para poner los datos en
CREATE TABLE heap_histogram (
histwhen timestamp without time zone NOT NULL,
class character varying NOT NULL,
instances integer NOT NULL,
bytes integer NOT NULL
);
Copia los datos en tu nueva tabla
/COPY heap_histogram FROM ''heap.csv'' WITH DELIMITER '','' CSV ;
Ejecute la consulta slop contra la consulta de tamaño (número de bytes):
SELECT class, REGR_SLOPE(bytes,extract(epoch from histwhen)) as slope
FROM public.heap_histogram
GROUP BY class
HAVING REGR_SLOPE(bytes,extract(epoch from histwhen)) > 0
ORDER BY slope DESC
;
Interpreta los resultados:
class | slope
---------------------------+----------------------
java.util.ArrayList | 71.7993806279174
java.util.HashMap | 49.0324576155785
java.lang.String | 31.7770770326123
joe.schmoe.BusinessObject | 23.2036817108056
java.lang.ThreadLocal | 20.9013528767851
La pendiente se agrega en bytes por segundo (ya que la unidad de época está en segundos). Si usa instancias en lugar de tamaño, entonces esa es la cantidad de instancias agregadas por segundo.
Mi una de las líneas de código que creó este joe.schmoe.BusinessObject fue responsable de la pérdida de memoria. Estaba creando el objeto, agregándolo a una matriz sin verificar si ya existía. Los otros objetos también se crearon junto con BusinessObject cerca del código con fugas.
He tenido éxito con IBM Heap Analyzer . Ofrece varias vistas del montón, incluida la mayor caída en el tamaño del objeto, los objetos que ocurren con mayor frecuencia y los objetos ordenados por tamaño.
He usado jhat , esto es un poco duro, pero depende del tipo de marco que tengas.
Si sucede después de una semana de uso, y su aplicación es tan bizantina como la describe, ¿quizás sea mejor reiniciarla cada semana?
Sé que no está solucionando el problema, pero puede ser una solución eficaz en el tiempo. ¿Hay ventanas de tiempo cuando puedes tener interrupciones? ¿Se puede cargar el saldo y fallar una instancia mientras se mantiene la segunda? Quizás pueda desencadenar un reinicio cuando el consumo de memoria supere un cierto límite (tal vez el monitoreo a través de JMX o similar).