java - heap dump analyzer
Cómo obtener un hilo y un volcado de pila de un proceso Java en Windows que no se está ejecutando en una consola (15)
Además de usar el jconsole / visualvm mencionado, puede usar jstack -l <vm-id>
en otra ventana de línea de comando y capturar esa salida.
El <vm-id> se puede encontrar usando el administrador de tareas (es el identificador del proceso en Windows y Unix), o usando jps
.
Tanto jstack
como jps
se incluyen en Sun JDK versión 6 y superior.
Tengo una aplicación Java que ejecuto desde una consola que a su vez ejecuta otro proceso Java. Quiero obtener un volcado de hilo / montón de ese proceso hijo.
En Unix, podría hacer un kill -3 <pid>
pero en Windows AFAIK, la única forma de obtener un volcado de hilo es Ctrl-Break en la consola. Pero eso solo me da el basurero del proceso padre, no el niño.
¿Hay otra manera de conseguir ese montón de basura?
Creo que la mejor manera de crear un archivo .hprof en el proceso de Linux es con el comando jmap . Por ejemplo: jmap -dump:format=b,file=filename.hprof {PID}
En un JDK de Oracle, tenemos un comando llamado jmap (disponible en la carpeta bin de Java Home). el uso del comando viene como sigue
jmap (opción) (pid)
Ejemplo: jmap -dump: live, format = b, file = heap.bin (pid)
Escribí un pequeño script por lotes para Java 8 (usando PsExec
y jcmd
) llamado jvmdump.bat
, que descarga los subprocesos, el montón, las propiedades del sistema y los argumentos de JVM.
:: set the paths for your environment
set PsExec=C:/Apps/SysInternals/PsExec.exe
set JAVA_HOME=C:/Apps/Java/jdk1.8.0_121
set DUMP_DIR=C:/temp
@echo off
set PID=%1
if "%PID%"=="" (
echo usage: jvmdump.bat {pid}
exit /b
)
for /f "tokens=2,3,4 delims=/ " %%f in (''date /t'') do set timestamp_d=%%h%%g%%f
for /f "tokens=1,2 delims=: " %%f in (''time /t'') do set timestamp_t=%%f%%g
set timestamp=%timestamp_d%%timestamp_t%
echo datetime is: %timestamp%
echo ### Version >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% VM.version >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo. >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo ### Uptime >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% VM.uptime >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo. >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo ### Command >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% VM.command_line >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo. >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo ### Flags >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% VM.flags >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo. >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
echo ### Properties >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% VM.system_properties >>"%DUMP_DIR%/%PID%-%timestamp%-jvm.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% Thread.print -l >"%DUMP_DIR%/%PID%-%timestamp%-threads.log"
%PsExec% -s %JAVA_HOME%/bin/jcmd.exe %PID% GC.heap_dump "%DUMP_DIR%/%PID%-%timestamp%-heap.hprof"
echo Dumped to %DUMP_DIR%
Debe ejecutarse en la misma sesión de Windows del usuario que inició la JVM, por lo que si se conecta a través de Escritorio remoto, es posible que deba iniciar un símbolo del sistema en la Session 0
y ejecutarlo desde allí. p.ej
%PsExec% -s -h -d -i 0 cmd.exe
Esto le indicará (haga clic en el icono de la barra de tareas en la parte inferior) para View the message
en la sesión interactiva, que lo llevará a la nueva consola en la otra sesión desde la que puede ejecutar el script jvmdump.bat
.
Estás confundiendo dos volcados de Java diferentes. kill -3
genera un volcado de hilo, no un volcado de pila.
Volcado de hilo = seguimientos de la pila para cada hilo en la salida JVM a la salida estándar como texto.
Volcado de volcado = contenido de memoria para la salida del proceso JVM a un archivo binario.
Para realizar un volcado de hilos en Windows, CTRL + BREAK si su JVM es el proceso de primer plano es la forma más sencilla. Si tiene un shell de tipo Unix en Windows como Cygwin o MobaXterm, puede usar kill -3 {pid}
como en Unix.
Para realizar un volcado de hilos en Unix, CTRL + C si su JVM es el proceso de primer plano o kill -3 {pid}
funcionará siempre que obtenga el PID correcto para la JVM.
Con cualquier plataforma, Java viene con varias utilidades que pueden ayudar. Para volcados de hilo, jstack {pid}
es su mejor apuesta. http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html
Solo para terminar la pregunta de descarte: los volcados de pila no se usan comúnmente porque son difíciles de interpretar. Pero tienen mucha información útil si saben dónde y cómo mirarlos. El uso más común es localizar las fugas de memoria. Es una buena práctica establecer la -D
en la línea de comandos de java para que el volcado de pila se genere automáticamente en un OutOfMemoryError, -XX:+HeapDumpOnOutOfMemoryError
Pero también puede activar manualmente un volcado de pila. La forma más común es usar la utilidad jmap
.
NOTA: esta utilidad no está disponible en todas las plataformas. A partir de JDK 1.6, jmap
está disponible en Windows.
Un ejemplo de línea de comandos se vería algo así como
jmap -dump:file=myheap.bin {pid of the JVM}
La salida "myheap.bin" no es legible para el ser humano (para la mayoría de nosotros), y necesitará una herramienta para analizarla. Mi preferencia es MAT. http://www.eclipse.org/mat/
Pruebe una de las siguientes opciones.
Para JVM de 32 bits:
jmap -dump:format=b,file=<heap_dump_filename> <pid>
Para JVM de 64 bits (citando explícitamente):
jmap -J-d64 -dump:format=b,file=<heap_dump_filename> <pid>
Para JVM de 64 bits con algoritmo G1GC en parámetros de VM (solo el montón de objetos vivos se genera con el algoritmo G1GC):
jmap -J-d64 -dump:live,format=b,file=<heap_dump_filename> <pid>
Pregunta de SE relacionada: Error de volcado de pila de Java con el comando jmap: EOF prematura
Echa un vistazo a varias opciones de jmap
en este article
Puede ejecutar jconsole
(incluido con el SDK de Java 6) y luego conectarse a su aplicación Java. Le mostrará todos los subprocesos en ejecución y su seguimiento de pila.
Puede usar jmap
para obtener un volcado de cualquier proceso en ejecución, asumiendo que conoce el pid
.
Utilice el Administrador de tareas o el Monitor de recursos para obtener el pid
. Entonces
jmap -dump:format=b,file=cheap.bin <pid>
para obtener el montón para ese proceso.
Puedes enviar el kill -3 <pid>
desde Cygwin. Tiene que usar las opciones de Cygwin ps
para encontrar procesos de Windows y luego enviar la señal a ese proceso.
Recomiendo Java VisualVM distribuido con el JDK (jvisualvm.exe). Puede conectarse dinámicamente y acceder a los hilos y el montón. He encontrado en invaluable para algunos problemas.
Si desea un heapdump en memoria insuficiente, puede iniciar Java con la opción -XX:-HeapDumpOnOutOfMemoryError
Si está utilizando JDK 1.6 o superior, puede usar el comando jmap
para tomar un volcado de pila de un proceso de Java, condición que debe conocer ProcessID.
Si está en Windows Machine, puede usar el Administrador de tareas para obtener PID. Para la máquina de Linux puede usar variedades de comando como ps -A | grep java
ps -A | grep java
o netstat -tupln | grep java
netstat -tupln | grep java
o top | grep java
top | grep java
, depende de tu aplicación.
Luego puede usar el comando como jmap -dump:format=b,file=sample_heap_dump.hprof 1234
donde 1234 es PID.
Hay variedades de herramientas disponibles para interpretar el archivo hprof. Recomendaré la herramienta visualvm de Oracle, que es fácil de usar.
Si estás en server-jre 8 y arriba puedes usar esto:
jcmd PID GC.heap_dump /tmp/dump
Si no puede (o no quiere) usar la consola / terminal por alguna razón, existe una solución alternativa. Puede hacer que la aplicación Java imprima el volcado de hilos por usted. El código que recopila el seguimiento de la pila es bastante simple y se puede adjuntar a un botón o una interfaz web.
private static String getThreadDump() {
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
StringBuilder out = new StringBuilder();
for (Map.Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet()) {
Thread thread = entry.getKey();
StackTraceElement[] elements = entry.getValue();
out.append(String.format("%s | prio=%d | %s", thread.getName(), thread.getPriority(), thread.getState()));
out.append(''/n'');
for (StackTraceElement element : elements) {
out.append(element.toString()).append(''/n'');
}
out.append(''/n'');
}
return out.toString();
}
Este método devolverá una cadena que se ve así:
main | prio=5 | RUNNABLE
java.lang.Thread.dumpThreads(Native Method)
java.lang.Thread.getAllStackTraces(Thread.java:1607)
Main.getThreadDump(Main.java:8)
Main.main(Main.java:36)
Monitor Ctrl-Break | prio=5 | RUNNABLE
java.net.PlainSocketImpl.initProto(Native Method)
java.net.PlainSocketImpl.<clinit>(PlainSocketImpl.java:45)
java.net.Socket.setImpl(Socket.java:503)
java.net.Socket.<init>(Socket.java:424)
java.net.Socket.<init>(Socket.java:211)
com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:59)
Finalizer | prio=8 | WAITING
java.lang.Object.wait(Native Method)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
Reference Handler | prio=10 | WAITING
java.lang.Object.wait(Native Method)
java.lang.Object.wait(Object.java:502)
java.lang.ref.Reference.tryHandlePending(Reference.java:191)
java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
Para aquellos interesados en una versión de Java 8 con secuencias, el código es aún más compacto:
private static String getThreadDump() {
Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();
StringBuilder out = new StringBuilder();
allStackTraces.forEach((thread, elements) -> {
out.append(String.format("%s | prio=%d | %s", thread.getName(), thread.getPriority(), thread.getState()));
out.append(''/n'');
Arrays.stream(elements).forEach(element -> out.append(element.toString()).append(''/n''));
out.append(''/n'');
});
return out.toString();
}
Puede probar fácilmente este código con:
System.out.print(getThreadDump());
Tienes que redirigir la salida del segundo ejecutable java a algún archivo. Luego, use SendSignal para enviar "-3" a su segundo proceso.