Как получить поток и дамп кучи Java-процесса в Windows, который не работает в консоли - PullRequest
218 голосов
/ 02 января 2009

У меня есть приложение Java, которое я запускаю с консоли, которая, в свою очередь, выполняет другой процесс Java. Я хочу получить поток / дамп этого дочернего процесса.

В Unix я мог сделать kill -3 <pid>, но в Windows AFAIK единственный способ получить дамп потока - это Ctrl-Break в консоли. Но это только дает мне дамп родительского процесса, а не дочернего.

Есть ли другой способ получить этот дамп кучи?

Ответы [ 18 ]

4 голосов
/ 02 января 2009

Вы должны перенаправить вывод из второго исполняемого файла Java в некоторый файл. Затем с помощью SendSignal до отправьте «-3» вашему второму процессу.

3 голосов
/ 12 февраля 2015

Если вы используете JDK 1.6 или выше, вы можете использовать команду jmap, чтобы получить дамп кучи Java-процесса, если вы должны знать ProcessID.

Если вы находитесь на Windows Machine, вы можете использовать диспетчер задач, чтобы получить PID. Для Linux-машины вы можете использовать различные команды, такие как ps -A | grep java или netstat -tupln | grep java или top | grep java, в зависимости от вашего приложения.

Затем вы можете использовать команду типа jmap -dump:format=b,file=sample_heap_dump.hprof 1234, где 1234 - PID.

Существует множество разновидностей инструмента , доступных для интерпретации файла hprof. Я буду рекомендовать инструмент Oracle VisualVM, который прост в использовании.

2 голосов
/ 23 марта 2017

Я написал небольшой пакетный скрипт для Java 8 (с использованием PsExec и jcmd) с именем jvmdump.bat, который выводит потоки, кучу, системные свойства и JVM args.

:: 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%

Он должен быть запущен в том же сеансе Windows пользователя, который запустил JVM, поэтому при подключении через удаленный рабочий стол может потребоваться запустить командную строку в Session 0 и запустить ее оттуда. например,

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

Это побудит вас (щелкните значок на панели задач внизу) на View the message в интерактивном сеансе, что приведет вас к новой консоли в другом сеансе, из которого вы можете запустить сценарий jvmdump.bat. *

1 голос
/ 03 августа 2018

Если вы не можете (или не хотите) использовать консоль / терминал по какой-либо причине, есть альтернативное решение. Вы можете заставить приложение Java печатать дамп потока для вас. Код, который собирает трассировку стека, достаточно прост и может быть прикреплен к кнопке или веб-интерфейсу.

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();
}

Этот метод вернет строку, которая выглядит следующим образом:

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)

Для тех, кто интересуется версией Java 8 с потоками, код еще более компактен:

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();
}

Вы можете легко проверить этот код с помощью:

System.out.print(getThreadDump());
0 голосов
/ 29 мая 2019

Может быть jcmd ?

Jcmd утилита используется для отправки запросов диагностических команд в JVM, где эти запросы полезны для управления записями полета Java, устранения неполадок и диагностики приложений JVM и Java.

Инструмент jcmd был представлен в Oracle 7 Java и особенно полезен для устранения проблем с приложениями JVM, поскольку он используется для определения идентификаторов процессов Java (сродни jps), получения дампов кучи (сродни jmap), получения дампов потоков похож на jstack), просматривая характеристики виртуальной машины, такие как системные свойства и флаги командной строки (сродни jinfo), и получая статистику сбора мусора (сродни jstat). Инструмент jcmd был назван «швейцарский армейский нож для исследования и решения проблем с вашим приложением JVM» и «скрытый драгоценный камень».

Вот процесс, который вам нужно будет использовать для вызова jcmd:

  1. Перейти к jcmd <pid> GC.heap_dump <file-path>
  2. В котором
  3. pid: идентификатор процесса Java, для которого будет захвачен дамп кучи. Также
  4. file-path: путь к файлу, в котором печатается дамп кучи.

Проверьте его для получения дополнительной информации о дампе кучи Java .

0 голосов
/ 16 мая 2019

Ниже Java-код используется для получения дампа кучи Java-процесса путем предоставления PID. Программа использует удаленное соединение JMX для выгрузки кучи. Это может быть полезно для некоторых.

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;
}

}

0 голосов
/ 28 января 2019

Visualvm:

Если вы «не можете подключиться» к запущенной JVM из jvisualvm, потому что вы не запустили его с правильными аргументами JVM (и он находится на удаленном ящике), запустите jstatd на удаленном ящике, затем, если установите прямое соединение, добавьте его в качестве «удаленного хоста» в visualvm, дважды щелкните имя хоста, и все остальные JVM на этом поле будут волшебным образом отображаться в visualvm.

Если у вас нет «прямого подключения» к портам на этом блоке, вы также можете сделать это через прокси .

Как только вы увидите нужный процесс, просмотрите его в jvisualvm и используйте вкладку монитора -> кнопку «heapdump».

0 голосов
/ 22 февраля 2018

В Oracle JDK у нас есть команда с именем jmap (доступна в папке bin Java Home). использование команды происходит следующим образом

jmap (опция) (pid)

Пример: jmap -dump: live, format = b, file = heap.bin (pid)

...