Como obter um encadeamento e heap dump de um processo Java no Windows que não está sendo executado em um console

232

Eu tenho um aplicativo Java que eu executo em um console que, por sua vez, executa outro processo Java. Eu quero obter um despejo de thread / heap desse processo filho.

No Unix, eu poderia fazer isso, kill -3 <pid>mas no Windows AFAIK a única maneira de obter um despejo de threads é Ctrl-Break no console. Mas isso só me dá o despejo do processo pai, não o filho.

Existe outra maneira de obter esse despejo de pilha?

Kasun Siyambalapitiya
fonte

Respostas:

376

Você pode usar jmappara obter um despejo de qualquer processo em execução, supondo que você saiba o pid.

Use o Gerenciador de tarefas ou o Monitor de recursos para obter o pid. Então

jmap -dump:format=b,file=cheap.hprof <pid>

para obter a pilha para esse processo.

rkaganda
fonte
O jmap não está disponível para o JDK5 no Windows. Existe alguma maneira de despejar com o JDK5 no Windows?
Santron Manibharathi
173
Esta discussão se tornou tão popular que eu acabei de ouvir alguém se referir a um despejo de pilha como um "cheap.bin"
mjaggard
7
Um nome de arquivo mais direto: "heap.hprof", pois está no formato HPROF.
MGM
1
Certifique-se de usar o usuário correto que iniciou o processo java. No meu caso, foi tomcat8 ps -C java -o pid sudo -u tomcat8 jmap -dump: format = b, file = <filename> <pid>
bitsabhi
115

Você está confundindo dois despejos java diferentes. kill -3gera um despejo de encadeamento, não um despejo de heap.

Despejo de encadeamento = rastreia de pilha para cada encadeamento na saída da JVM para stdout como texto.

Despejo de heap = conteúdo da memória para a saída do processo da JVM em um arquivo binário.

Para executar um despejo de encadeamento no Windows, CTRL+ BREAKse sua JVM for o processo em primeiro plano, é a maneira mais simples. Se você possui um shell do tipo unix no Windows, como Cygwin ou MobaXterm, pode usarkill -3 {pid} lo como no Unix.

Para fazer um despejo de encadeamento no Unix, CTRL+ Cse sua JVM for o processo em primeiro plano oukill -3 {pid} funcionará desde que você obtenha o PID correto para a JVM.

Em qualquer plataforma, o Java vem com vários utilitários que podem ajudar. Para despejos de linha, jstack {pid}é sua melhor aposta. http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html

Apenas para finalizar a pergunta de despejo: despejos de pilha não são comumente usados ​​porque são difíceis de interpretar. Mas, eles têm muitas informações úteis, se você souber onde / como olhar para eles. O uso mais comum é localizar vazamentos de memória. É uma boa prática definir -Dna linha de comando java para que o dump do heap seja gerado automaticamente em um OutOfMemoryError. -XX:+HeapDumpOnOutOfMemoryError Porém, você também pode acionar manualmente um dump do heap. A maneira mais comum é usar o utilitário java jmap.

NOTA: este utilitário não está disponível em todas as plataformas. A partir do JDK 1.6,jmap está disponível no Windows.

Um exemplo de linha de comando seria algo como

jmap -dump:file=myheap.bin {pid of the JVM}

A saída "myheap.bin" não é legível por humanos (para a maioria de nós) e você precisará de uma ferramenta para analisá-la. Minha preferência é MAT. http://www.eclipse.org/mat/

Derek
fonte
3
No meu linux Ctrl-C interrompe (termina), eu faço Ctrl- \
nafg
Considere isso e seu impacto geral em "Para executar um despejo de encadeamento no Windows, CTRL + BREAK". Na verdade, depende da decisão de engenharia do fabricante. FE, Lenova, IIRC, é cntrl + fn + p.
ChiefTwoPencils #
30

Eu acho que a melhor maneira de criar arquivo .hprof no processo Linux é com o comando jmap . Por exemplo:jmap -dump:format=b,file=filename.hprof {PID}

Roberto Flores
fonte
19

Além de usar o jconsole / visualvm mencionado, você pode usar jstack -l <vm-id> em outra janela da linha de comandos e capturar essa saída.

O <vm-id> pode ser encontrado usando o gerenciador de tarefas (é o ID do processo no Windows e no Unix) ou usando jps .

Ambos jstacke jpsestão incluídos no Sun JDK versão 6 e superior.

ankon
fonte
Essas ferramentas não são suportadas no Java 1.6. O Java 1.6 possui apenas o jconsole.
Vanchinathan Chandrasekaran
7
Você pode estar misturando JDK e JRE, mencionei explicitamente o JDK. Consulte a documentação das ferramentas: download.oracle.com/javase/6/docs/technotes/tools/share/… e download.oracle.com/javase/6/docs/technotes/tools/share/…
ankon
17

Eu recomendo o Java VisualVM distribuído com o JDK (jvisualvm.exe). Ele pode se conectar dinamicamente e acessar os threads e a pilha. Eu achei inestimável para alguns problemas.

Lawrence Dol
fonte
2
Na maioria das vezes, isso não é viável, pois possui uma sobrecarga e os despejos de rosca geralmente são recuperados das máquinas de produção.
Hammad Dar
A pergunta original é sobre um processo de 'não correr'. É provável que o jvisualvm não consiga se conectar.
Jaberino
3
@Jaberino: Não, trata-se de um processo Java atualmente em execução, no Windows, sem console associado a ele.
Lawrence Dol
Nos últimos lançamentos em Java, o Java VisualVM foi substituído pelo JMC / JFR . Consulte também Quais são as diferenças entre o JVisualVM e o Java Mission Control?
Vadzim
16

Se você estiver no server-jre 8 e acima, poderá usar o seguinte:

jcmd PID GC.heap_dump /tmp/dump
Atul Soman
fonte
1
Na maioria dos sistemas de produção, temos apenas jre e não jdk. Então isso ajuda.
Pragalathan M
15

Experimente uma das opções abaixo.

  1. Para JVM de 32 bits:

    jmap -dump:format=b,file=<heap_dump_filename> <pid>
  2. Para JVM de 64 bits (citando explicitamente):

    jmap -J-d64 -dump:format=b,file=<heap_dump_filename> <pid>
  3. Para a JVM de 64 bits com o algoritmo G1GC nos parâmetros da VM (apenas o heap de objetos ativos é gerado com o algoritmo G1GC):

    jmap -J-d64 -dump:live,format=b,file=<heap_dump_filename> <pid>

Pergunta SE relacionada: Erro de despejo de heap Java com o comando jmap: EOF prematuro

Dê uma olhada em várias opções jmapdeste artigo

Ravindra babu
fonte
13

Se você desejar um heapdump com falta de memória, poderá iniciar o Java com a opção -XX:-HeapDumpOnOutOfMemoryError

cf Página de referência de Opções da JVM

Daniel Winterstein
fonte
Obrigado Daniel. Onde esse arquivo é criado em uma máquina Windows? Existe um caminho padrão?
Lava
1
@lava Você pode definir o caminho através de -XX: HeapDumpPath, conforme descrito na página Opções de VM da Oracle .
kamczak
Impressionante. Eu queria fazer um teste durante a noite, na esperança de mostrar vazamento de memória, mas estava preocupado com o OOM e travando enquanto não estava presente. Isto é perfeito.
Basil
7

Você pode executar jconsole(incluído no SDK do Java 6) e conectar-se ao seu aplicativo Java. Ele mostrará todos os Threads em execução e seu rastreamento de pilha.

Steve Kuo
fonte
melhor resposta de longe! Não sabia disso até agora e é realmente prático!
Xerus
7

Você pode enviar o kill -3 <pid>da Cygwin. Você precisa usar as psopções do Cygwin para encontrar os processos do Windows, basta enviar o sinal para esse processo.

krosenvold
fonte
3

Se você estiver usando o JDK 1.6 ou superior, poderá usar jmap comando para fazer um heap Dump de um processo Java, a condição é que você deve conhecer ProcessID.

Se você estiver no Windows Machine, poderá usar o Gerenciador de tarefas para obter o PID. Para máquinas Linux, você pode usar variedades de comando como ps -A | grep javaou netstat -tupln | grep javaoutop | grep java , depende do seu aplicativo.

Em seguida, você pode usar o comando como jmap -dump:format=b,file=sample_heap_dump.hprof 1234onde 1234 é PID.

Existem diversas ferramentas disponíveis para interpretar o arquivo hprof. Vou recomendar a ferramenta visualvm da Oracle, que é simples de usar.

Badal
fonte
3

Se você não puder (ou não quiser) usar o console / terminal por algum motivo, existe uma solução alternativa. Você pode fazer com que o aplicativo Java imprima o despejo de encadeamento para você. O código que coleta o rastreamento de pilha é bastante simples e pode ser anexado a um botão ou interface da 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 retornará uma string parecida com esta:

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 os interessados ​​em uma versão Java 8 com fluxos, o código é ainda mais 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();
}

Você pode facilmente testar esse código com:

System.out.print(getThreadDump());
HugoTeixeira
fonte
3

O script a seguir usa o PsExec para conectar-se a outra sessão do Windows, para que funcione mesmo quando conectado pelo Serviço de Área de Trabalho Remota.

Escrevi um pequeno script em lote para o Java 8 (usando PsExece jcmd) chamado jvmdump.bat, que despeja os encadeamentos, heap, propriedades do sistema e args da 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%

Ele deve ser executado na mesma sessão do Windows do usuário que iniciou a JVM; portanto, se você se conectar pela Área de Trabalho Remota, poderá ser necessário iniciar um prompt de comando Session 0e executá-lo a partir daí. por exemplo

%PsExec% -s -h -d -i 0 cmd.exe

Isso solicitará que você (clique no ícone da barra de tarefas na parte inferior) View the messageda sessão interativa, que o levará ao novo console na outra sessão a partir da qual você pode executar o jvmdump.batscript.

isapir
fonte
2

Como obter a identificação do processo do aplicativo java?

Execute o comando 'jcmd' para obter a identificação do processo de aplicativos java.

Como obter o despejo de threads?

jcmd PID Thread.print> thread.dump

Link de referência

Você pode até usar o jstack para obter o dump de threads (jstack PID> thread.dump). Link de referência

Como obter o heap dump?

Use a ferramenta jmap para obter o heap dump. jmap -F -dump: ativo, formato = b, arquivo = heap.bin PID

PID significa ID do processo do aplicativo. Link de referência

Hari Krishna
fonte
1

Talvez jcmd ?

O utilitário Jcmd é usado para enviar solicitações de comando de diagnóstico para a JVM, onde essas solicitações são úteis para controlar o Java Flight Recordings, solucionar problemas e diagnosticar JVM e aplicativos Java.

A ferramenta jcmd foi introduzida no Java 7 da Oracle e é particularmente útil na solução de problemas com aplicativos JVM, usando-a para identificar IDs de processos Java (semelhante ao jps), adquirindo dumps de pilha (semelhante ao jmap), adquirindo dumps de segmento (semelhante ao jstack ), visualizando características da máquina virtual, como propriedades do sistema e sinalizadores de linha de comando (semelhante ao jinfo) e adquirindo estatísticas de coleta de lixo (semelhante ao jstat). A ferramenta jcmd foi chamada "um canivete suíço para investigar e resolver problemas com seu aplicativo JVM" e uma "jóia escondida".

Aqui está o processo que você precisará usar para chamar jcmd:

  1. Vamos para jcmd <pid> GC.heap_dump <file-path>
  2. No qual
  3. pid: é um ID do processo Java, para o qual o dump do heap será capturado. Além disso, o
  4. caminho do arquivo: é um caminho de arquivo no qual o despejo de heap é impresso.

Confira para obter mais informações sobre como fazer o heap dump do Java .

Johnny
fonte
0

Acompanhamento do Visualvm:

Se você "não conseguir se conectar" à sua JVM em execução a partir do jvisualvm porque não a iniciou com os argumentos corretos da JVM (e está na caixa remota), execute jstatdna caixa remota e, assumindo que você possui uma conexão direta, inclua como um "host remoto" no visualvm, clique duas vezes no nome do host e todas as outras JVMs nessa caixa aparecerão magicamente no visualvm.

Se você não tiver "conexão direta" com as portas nessa caixa, também poderá fazer isso por meio de um proxy .

Depois de ver o processo desejado, faça uma busca detalhada no jvisualvm e use a guia monitor -> botão "heapdump".

rogerdpack
fonte
0

Abaixo, o código java é usado para obter o Heap Dump de um processo Java, fornecendo PID. O programa usa a conexão JMX remota para despejar heap. Pode ser útil para alguém.

import java.lang.management.ManagementFactory;
import javax.management.MBeanServerConnection;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.lang.reflect.Method;

public class HeapDumper {

public static final String HOST = "192.168.11.177";
public static final String PORT = "1600";
public static final String FILE_NAME = "heapDump.hprof";
public static final String FOLDER_PATH = "C:/";
private static final String HOTSPOT_BEAN_NAME ="com.sun.management:type=HotSpotDiagnostic";

public static void main(String[] args) {
    if(args.length == 0) {
        System.out.println("Enter PID of the Java Process !!!");
        return;
    }

    String pidString = args[0];
    int pid = -1;
    if(pidString!=null && pidString.length() > 0) {
        try {
            pid = Integer.parseInt(pidString);
        }
        catch(Exception e) {
            System.out.println("PID is not Valid !!!");
            return;
        }
    }
    boolean isHeapDumpSuccess = false;
    boolean live = true;
    if(pid > 0) {
        MBeanServerConnection beanServerConn = getJMXConnection();

        if(beanServerConn!=null) {
            Class clazz = null;
            String dumpFile = FOLDER_PATH+"/"+FILE_NAME;
            try{
                clazz = Class.forName("com.sun.management.HotSpotDiagnosticMXBean");
                Object hotspotMBean = ManagementFactory.newPlatformMXBeanProxy(beanServerConn, HOTSPOT_BEAN_NAME, clazz);
                Method method = clazz.getMethod("dumpHeap", new Class[]{String.class , boolean.class});
                method.setAccessible(true);
                method.invoke(hotspotMBean , new Object[] {dumpFile, new Boolean(live)});
                isHeapDumpSuccess = true;
            }
            catch(Exception e){
                e.printStackTrace();
                isHeapDumpSuccess = false;
            }
            finally{
                clazz = null;
            }
        }
    }

    if(isHeapDumpSuccess){
        System.out.println("HeapDump is Success !!!");
    }
    else{
        System.out.println("HeapDump is not Success !!!");
    }
}

private static MBeanServerConnection getJMXConnection() {
    MBeanServerConnection mbeanServerConnection = null;
    String urlString = "service:jmx:rmi:///jndi/rmi://" + HOST + ":" + PORT + "/jmxrmi";
    try {
        JMXServiceURL url = new JMXServiceURL(urlString);
        JMXConnector jmxConnector = JMXConnectorFactory.connect(url);
        mbeanServerConnection = jmxConnector.getMBeanServerConnection();
        System.out.println("JMX Connection is Success for the URL :"+urlString);
    }
    catch(Exception e) {
        System.out.println("JMX Connection Failed !!!");
    }
    return mbeanServerConnection;
}

}

Ramesh Subramanian
fonte
0

Para fazer o despejo de encadeamento / despejo de heap de um processo java filho no Windows, é necessário identificar o ID do processo filho como primeira etapa.

Emitindo o comando: jps você poderá obter todos os IDs de processos java que estão sendo executados na sua máquina Windows. Nessa lista, você precisa selecionar o ID do processo filho. Depois de ter o ID do processo filho, existem várias opções para capturar despejo de encadeamentos e despejos de heap.

Capturando Despejos de Thread:

Existem 8 opções para capturar despejos de encadeamento:

  1. jstack
  2. matar -3
  3. jvisualVM
  4. JMC
  5. Windows (Ctrl + Interrupção)
  6. ThreadMXBean
  7. Ferramentas APM
  8. jcmd

Detalhes sobre cada opção podem ser encontrados neste artigo . Depois de capturar despejos de encadeamento, você pode usar ferramentas como fastThread , o Samuraito analisa despejos de encadeamento.

Capturando despejos de pilha:

Existem 7 opções para capturar despejos de heap:

  1. jmap

  2. -XX: + HeapDumpOnOutOfMemoryError

  3. jcmd

  4. JVisualVM

  5. JMX

  6. Abordagem programática

  7. Consoles administrativos

Detalhes sobre cada opção podem ser encontrados neste artigo . Depois de capturar o despejo de heap, você pode usar ferramentas como a ferramenta Eclipse Memory Analysis , HeapHero para analisar os despejos de heap capturados.

Jim T
fonte
-1

Em um Oracle JDK, temos um comando chamado jmap (disponível na pasta bin do Java Home). o uso do comando é o seguinte

jmap (opção) (pid)

Exemplo: jmap -dump: live, formato = b, arquivo = heap.bin (pid)

Suresh Ram
fonte