Как получить поток и дамп кучи Java-процесса в Windows, который не работает в консоли

232

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

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

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

Касун Сиямбалапития
источник

Ответы:

376

Вы можете использовать, jmapчтобы получить дамп любого запущенного процесса, предполагая, что вы знаете pid.

Используйте диспетчер задач или монитор ресурсов, чтобы получить pid. затем

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

чтобы получить кучу для этого процесса.

rkaganda
источник
Jmap не доступен для JDK5 в Windows. Есть ли способ взять дамп с JDK5 на windows?
Сантрон Манибхарати
173
Эта тема стала настолько популярной, что я только что услышал, как кто-то называет дамп кучи "cheap.bin"
mjaggard
7
Более простое имя файла: «heap.hprof», как в формате HPROF.
MGM
1
Убедитесь, что используете правильный пользователь, который запустил процесс Java. В моем случае это был tomcat8 ps -C java -o pid sudo -u tomcat8 jmap -dump: format = b, file = <имя_файла> <pid>
bitsabhi
115

Вы путаете два разных дампов Java. kill -3генерирует дамп потока, а не дамп кучи.

Дамп потока = трассировка стека для каждого потока в выводе JVM в стандартный вывод в виде текста.

Дамп кучи = содержимое памяти для вывода процесса JVM в двоичный файл.

Чтобы получить дамп потока в Windows, CTRL+, BREAKесли ваша JVM является приоритетным процессом, это самый простой способ. Если у вас есть Unix-подобная оболочка в Windows, такая как Cygwin или MobaXterm, вы можете использовать ее так же, kill -3 {pid}как в Unix.

Чтобы получить дамп потока в Unix, CTRL+, Cесли ваша JVM является приоритетным процессом или kill -3 {pid}будет работать до тех пор, пока вы получите правильный PID для JVM.

В любой платформе Java поставляется с несколькими утилитами, которые могут помочь. Для дампов нитей jstack {pid}это ваш лучший выбор. http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html

Просто чтобы закончить вопрос о дампе: дампы кучи обычно не используются, потому что их трудно интерпретировать. Но они содержат много полезной информации, если вы знаете, где и как на них смотреть. Наиболее распространенное использование - обнаружение утечек памяти. Рекомендуется установить -Dв командной строке java, чтобы дамп кучи генерировался автоматически при OutOfMemoryError, -XX:+HeapDumpOnOutOfMemoryError но вы также можете вручную запустить дамп кучи. Самый распространенный способ - использовать утилиту Java jmap.

ПРИМЕЧАНИЕ: эта утилита доступна не на всех платформах. Начиная jmapс версии JDK 1.6 доступен для Windows.

Пример командной строки будет выглядеть примерно так

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

Вывод «myheap.bin» не читается человеком (для большинства из нас), и вам понадобится инструмент для его анализа. Я предпочитаю MAT. http://www.eclipse.org/mat/

Дерек
источник
3
На моем Linux Ctrl-C прерывает (завершает) его, я делаю Ctrl- \
nafg
Рассмотрим это и его общее влияние на «Создание дампа потока в Windows, CTRL + BREAK». Это на самом деле зависит от инженерного решения производителя. FE, Lenova, IIRC, это cntrl + fn + p.
ChiefTwoPencils
30

Я думаю, что лучший способ создать файл .hprof в процессе Linux с помощью команды jmap . Например:jmap -dump:format=b,file=filename.hprof {PID}

Роберто Флорес
источник
19

В дополнение к использованию упомянутого jconsole / visualvm вы можете использовать jstack -l <vm-id>другое окно командной строки и записать этот вывод.

<Vm-id> можно найти с помощью диспетчера задач (это идентификатор процесса в Windows и Unix) или с помощью jps.

Оба jstackи jpsвключены в Sun JDK версии 6 и выше.

Ankon
источник
Эти инструменты не поддерживаются в Java 1.6. Java 1.6 имеет только jconsole.
Ванчинатан Чандрасекаран
7
Вы можете смешивать JDK и JRE, я прямо упомянул JDK. См. Документацию по инструментам: download.oracle.com/javase/6/docs/technotes/tools/share/… and download.oracle.com/javase/6/docs/technotes/tools/share/…
ankon
17

Я рекомендую Java VisualVM, распространяемый вместе с JDK (jvisualvm.exe). Он может подключаться динамически и получать доступ к потокам и куче. Я нашел в неоценимом для некоторых проблем.

Лоуренс Дол
источник
2
В большинстве случаев это неосуществимо, поскольку к нему прикреплены служебные данные, а дампы потоков обычно извлекаются с рабочих машин.
Хаммад Дар
Первоначальный вопрос касается процесса «не запуска». Вероятно, что jvisualvm не может подключиться.
Jaberino
3
@Jaberino: Нет, речь идет о работающем в данный момент Java-процессе в Windows, без связанной с ним консоли.
Лоуренс Дол
В последних выпусках Java Java VisualVM была заменена JMC / JFR . См. Также Каковы различия между JVisualVM и Java Mission Control?
Вадим
16

Если вы находитесь на сервере JRE 8 и выше, вы можете использовать это:

jcmd PID GC.heap_dump /tmp/dump
Атул Соман
источник
1
В большинстве производственных систем у нас есть только jre, а не jdk. Так что это помогает.
Pragalathan M
15

Попробуйте один из следующих вариантов.

  1. Для 32-битной JVM:

    jmap -dump:format=b,file=<heap_dump_filename> <pid>
  2. Для 64-битной JVM (с явным цитированием):

    jmap -J-d64 -dump:format=b,file=<heap_dump_filename> <pid>
  3. Для 64-битной JVM с алгоритмом G1GC в параметрах виртуальной машины (с помощью алгоритма G1GC генерируется только куча живых объектов):

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

Связанный вопрос SE: Ошибка дампа кучи Java с командой jmap: Преждевременный EOF

Посмотрите на различные варианты jmapв этой статье

Равиндра Бабу
источник
13

Если вы хотите получить кучу данных при нехватке памяти, вы можете запустить Java с опцией -XX:-HeapDumpOnOutOfMemoryError

cf страница ссылок на опции JVM

Даниэль Винтерштейн
источник
Спасибо Даниэль. Где этот файл создан на машине с Windows? Есть ли путь по умолчанию?
лава
1
@lava Вы можете установить путь через -XX: HeapDumpPath, как описано на странице параметров виртуальной машины Oracle .
Камчак
Потрясающие. Я хотел провести тест в течение ночи в надежде показать утечку памяти, но беспокоился о OOM и сбоях, пока меня нет. Это потрясающе.
Василий
7

Вы можете запустить jconsole(входит в состав Java 6 SDK) и подключиться к вашему Java-приложению. Он покажет вам каждый запущенный поток и его трассировку стека.

Стив Куо
источник
лучший ответ на сегодняшний день! До сих пор не знал об этом, и это действительно практично!
Xerus
7

Вы можете отправить kill -3 <pid>из Cygwin. Вы должны использовать параметры Cygwin ps, чтобы найти процессы Windows, а затем просто отправить сигнал этому процессу.

krosenvold
источник
5

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

Йони Ройт
источник
Но jstack оказался более надежной альтернативой для Windows: stackoverflow.com/a/47723393/603516
Вадим
3

Если вы используете 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, который прост в использовании.

Badal
источник
3

Если по какой-то причине вы не можете (или не хотите) использовать консоль / терминал, есть альтернативное решение. Вы можете заставить приложение 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());
HugoTeixeira
источник
3

Следующий скрипт использует PsExec для подключения к другому сеансу Windows, поэтому он работает даже при подключении через службу удаленного рабочего стола.

Я написал небольшой пакетный скрипт для Java 8 (с использованием PsExecand jcmd) с именем jvmdump.bat, который выводит потоки, кучу, системные свойства и аргументы 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%

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

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

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

isapir
источник
2

Как получить идентификатор процесса Java-приложения?

Выполните команду 'jcmd', чтобы получить идентификатор процесса Java-приложений.

Как получить дамп темы?

jcmd PID Thread.print> thread.dump

Ссылка ссылка

Вы даже можете использовать jstack для получения дампа потока (jstack PID> thread.dump). Ссылка ссылка

Как получить кучу дампов?

Используйте инструмент jmap, чтобы получить дамп кучи. jmap -F -dump: live, format = b, file = heap.bin PID

PID обозначает идентификатор процесса приложения. Ссылка ссылка

Хари Кришна
источник
1

Может быть, JCMD ?

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

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

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

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

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

Джонни
источник
0

Visualvm продолжение:

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

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

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

rogerdpack
источник
0

Ниже приведен код 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

Чтобы взять дамп потока / дамп кучи от дочернего Java-процесса в Windows, вы должны определить Id дочернего процесса в качестве первого шага.

С помощью команды: jps вы сможете получить все идентификаторы процессов Java, которые работают на вашем компьютере с Windows. Из этого списка вам нужно выбрать идентификатор дочернего процесса. Если у вас есть дочерний процесс Id, есть различные опции для захвата дампов потока и дампов кучи.

Захват резьбы дампов:

Есть 8 вариантов захвата дампов потоков:

  1. jstack
  2. убить -3
  3. jvisualVM
  4. JMC
  5. Windows (Ctrl + Break)
  6. ThreadMXBean
  7. Инструменты APM
  8. jcmd

Подробности о каждой опции можно найти в этой статье . Получив дампы потоков, вы можете использовать такие инструменты, как fastThread и Samuraito для анализа дампов потоков.

Захват свалок кучи:

Существует 7 вариантов захвата дампов кучи:

  1. jmap

  2. -XX: + HeapDumpOnOutOfMemoryError

  3. jcmd

  4. JVisualVM

  5. JMX

  6. Программный подход

  7. Административные консоли

Подробности о каждой опции можно найти в этой статье . После захвата дампа кучи вы можете использовать такие инструменты, как Eclipse Memory Analysis , HeapHero, для анализа захваченных дампов кучи.

Джим Т
источник
-1

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

jmap (опция) (pid)

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

Суреш Рам
источник