257

我有一个从控制台运行的 Java 应用程序,该控制台又执行另一个 Java 进程。我想获得该子进程的线程/堆转储。

在 Unix 上,我可以做一个kill -3 <pid>但在 Windows AFAIK 上,获得线程转储的唯一方法是控制台中的 Ctrl-Break。但这只会给我父进程的转储,而不是子进程。

还有另一种方法来获取堆转储吗?

4

20 回答 20

428

jmap假设您知道pid.

使用任务管理器或资源监视器来获取pid. 然后

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

获取该进程的堆。

对于安装bashpgrep并且运行单个 Java 进程的系统,请尝试:

jmap -dump:format=b,file=heap.hprof $(pgrep java)
于 2010-06-15T04:12:24.430 回答
126

您混淆了两个不同的 java 转储。 kill -3生成线程转储,而不是堆转储。

线程转储 = JVM 中每个线程的堆栈跟踪以文本形式输出到标准输出。

堆转储 = JVM 进程输出到二进制文件的内存内容。

要在 Windows 上进行线程转储,CTRL+BREAK如果您的 JVM 是前台进程是最简单的方法。如果你在 Windows 上有一个类似 unix 的 shell,比如 Cygwin 或 MobaXterm,你可以kill -3 {pid}像在 Unix 中一样使用。

要在 Unix 中进行线程转储,CTRL+C如果您的 JVM 是前台进程,或者kill -3 {pid}只要您为 JVM 获得正确的 PID 就可以工作。

对于这两种平台,Java 都带有几个可以提供帮助的实用程序。对于线程转储,jstack {pid}是您最好的选择。http://docs.oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html

只是为了完成转储问题:堆转储不常用,因为它们难以解释。但是,如果您知道在哪里/如何查看它们,它们中就会包含很多有用的信息。最常见的用法是定位内存泄漏。最好-D在 java 命令行上设置,以便在 OutOfMemoryError 时自动生成堆转储,-XX:+HeapDumpOnOutOfMemoryError 但是,您也可以手动触发堆转储。最常见的方法是使用 java 实用程序jmap

注意:此实用程序并非在所有平台上都可用。从 JDK 1.6 开始,jmap可在 Windows 上使用。

一个示例命令行看起来像

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

输出“myheap.bin”不是人类可读的(对于我们大多数人来说),您需要一个工具来分析它。我的首选是MAT。 http://www.eclipse.org/mat/

于 2013-09-18T19:15:12.247 回答
34

我认为在 Linux 进程中创建 .hprof 文件的最佳方法是使用jmap命令。例如:jmap -dump:format=b,file=filename.hprof {PID}

于 2013-10-14T22:31:34.940 回答
22

除了使用提到的 jconsole/visualvm 之外,您还可以jstack -l <vm-id>在另一个命令行窗口上使用,并捕获该输出。

<vm-id> 可以使用任务管理器找到(它是 windows 和 unix 上的进程 ID),或者使用jps.

两者jstackjps包含在 Sun JDK 版本 6 及更高版本中。

于 2009-11-29T17:46:04.897 回答
19

我推荐随 JDK (jvisualvm.exe) 一起分发的 Java VisualVM。它可以动态连接并访问线程和堆。我发现某些问题非常宝贵。

于 2009-01-02T19:25:54.630 回答
18

如果您使用的是 server-jre 8 及更高版本,则可以使用:

jcmd PID GC.heap_dump /tmp/dump
于 2016-02-09T05:43:13.633 回答
17

尝试以下选项之一。

  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. 对于在 VM 参数中使用 G1GC 算法的 64 位 JVM(仅使用 G1GC 算法生成活动对象堆):

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

相关的 SE 问题:Java heap dump error with jmap command : Premature EOF

查看本文的jmap各种选项

于 2016-07-08T14:02:01.733 回答
15

如果您想在内存不足时进行堆转储,可以使用选项启动 Java-XX:-HeapDumpOnOutOfMemoryError

cf JVM 选项参考页

于 2012-01-18T15:59:12.330 回答
10

您可以kill -3 <pid>从 Cygwin 发送。您必须使用 Cygwinps选项来查找 Windows 进程,然后将信号发送到该进程。

于 2009-01-02T18:19:51.907 回答
9

您可以运行jconsole(包含在 Java 6 的 SDK 中)然后连接到您的 Java 应用程序。它将向您显示每个正在运行的线程及其堆栈跟踪。

于 2009-01-02T18:45:22.340 回答
7

您必须将输出从第二个 java 可执行文件重定向到某个文件。然后,使用SendSignal“-3”发送到您的第二个进程。

于 2009-01-02T18:26:26.340 回答
5

如何获取java应用程序的进程ID?

执行命令 'jcmd' 以获取 java 应用程序的进程 ID。

如何获得线程转储?

jcmd PID Thread.print > thread.dump

参考链接

您甚至可以使用 jstack 来获取线程转储(jstack PID > thread.dump)。参考链接

如何获得堆转储?

使用 jmap 工具获取堆转储。jmap -F -dump:live,format=b,file=heap.bin PID

PID 代表应用程序的进程 ID。参考链接

于 2020-01-03T07:59:44.063 回答
5

以下脚本使用 PsExec 连接到另一个 Windows 会话,因此即使通过远程桌面服务连接也可以正常工作。

PsExec我为 Java 8(使用和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%

它必须在启动 JVM 的用户的同一 Windows 会话中运行,因此如果您通过远程桌面连接,您可能需要在其中启动命令提示符Session 0并从那里运行它。例如

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

这将提示您(单击底部的任务栏图标)进入View the message交互式会话,这将带您进入另一个会话中的新控制台,您可以从中运行jvmdump.bat脚本。

于 2017-03-23T18:28:56.383 回答
4

如果您使用JDK 1.6 或更高版本,您可以使用jmap命令对Java 进程进行堆转储,条件是您应该知道ProcessID。

如果你在 Windows 机器上,你可以使用任务管理器来获取 PID。对于 Linux 机器,您可以使用各种命令,如ps -A | grep javaor netstat -tupln | grep javatop | grep java,具体取决于您的应用程序。

然后你可以使用像jmap -dump:format=b,file=sample_heap_dump.hprof 12341234 是 PID 这样的命令。

有多种工具可用于解释 hprof 文件。我会推荐Oracle的visualvm工具,使用简单。

于 2015-02-12T09:48:17.463 回答
4

如果由于某种原因您不能(或不想)使用控制台/终端,则可以使用替代解决方案。您可以让 Java 应用程序为您打印线程转储。收集堆栈跟踪的代码相当简单,可以附加到按钮或 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();
}

此方法将返回如下所示的字符串:

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());
于 2018-08-03T02:00:25.307 回答
1

下面的 java 代码用于通过提供远程进程的 PID 来获取 Java 进程的堆转储。该程序使用远程 JMX 连接将堆转储到文件。它可能对某些人有帮助。不需要jmap。

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

}

于 2019-05-16T07:41:08.900 回答
1

为了从 Windows 中的子 java 进程中获取线程转储/堆转储,您需要识别子进程 ID 作为第一步。

通过发出命令:jps您将能够获取在您的 Windows 机器上运行的所有 Java 进程 ID。从此列表中,您需要选择子进程 ID。一旦有了子进程 ID,就有多种选项可以捕获线程转储和堆转储。

捕获线程转储:

有 8 个选项可以捕获线程转储:

  1. 堆栈
  2. 杀死 -3
  3. jvisualVM
  4. 江铃
  5. 窗口(Ctrl + Break)
  6. 线程MXBean
  7. APM 工具
  8. 指令

可以在本文中找到有关每个选项的详细信息。捕获线程转储后,您可以使用fastThreadSamuraito等工具分析线程转储。

捕获堆转储:

有 7 个选项可以捕获堆转储:

  1. 地图

  2. -XX:+HeapDumpOnOutOfMemoryError

  3. 指令

  4. 视觉虚拟机

  5. JMX

  6. 程序化方法

  7. 管理控制台

可以在本文中找到有关每个选项的详细信息。捕获堆转储后,您可以使用Eclipse 内存分析工具HeapHero等工具来分析捕获的堆转储。

于 2019-12-12T06:25:42.437 回答
1

也许jcmd

Jcmd实用程序用于向 JVM 发送诊断命令请求,这些请求可用于控制 Java 飞行记录、故障排除和诊断 JVM 和 Java 应用程序。

jcmd 工具是在 Oracle 的 Java 7 中引入的,通过使用它来识别 Java 进程的 ID(类似于 jps)、获取堆转储(类似于 jmap)、获取线程转储(类似于 jstack),在解决 JVM 应用程序问题时特别有用),查看系统属性和命令行标志等虚拟机特征(类似于 jinfo),并获取垃圾收集统计信息(类似于 jstat)。jcmd 工具被称为“用于调查和解决 JVM 应用程序问题的瑞士军刀”和“隐藏的宝石”。

这是您在调用 时需要使用的过程jcmd

  1. jcmd <pid> GC.heap_dump <file-path>
  2. 其中
  3. pid:是一个 Java 进程 ID,将为其捕获堆转储此外,
  4. 文件路径:是打印堆转储的文件路径。

查看有关获取Java 堆转储的更多信息。

于 2019-05-29T13:49:53.973 回答
0

Visualvm 跟进:

如果您从 jvisualvm “无法连接”到正在运行的 JVM,因为您没有使用正确的 JVM 参数启动它(并且它在远程机器上),jstatd请在远程机器上运行,然后,假设您有直接连接,添加它作为visualvm中的“远程主机”,双击主机名,该框上的所有其他JVM都会神奇地出现在visualvm中。

如果你没有“直接连接”到那个盒子上的端口,你也可以通过代理来做到这一点。

一旦你可以看到你想要的进程,在 jvisualvm 中钻取它并使用监视器选项卡 - >“heapdump”按钮。

于 2019-01-28T18:43:25.337 回答
-1

在 Oracle JDK 上,我们有一个名为 jmap 的命令(位于 Java Home 的 bin 文件夹中)。命令用法如下

jmap (选项) (pid)

示例:jmap -dump:live,format=b,file=heap.bin (pid)

于 2018-02-22T09:37:41.530 回答