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

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

有其他方法来获取堆转储吗?


当前回答

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

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

jstack和jps都包含在Sun JDK版本6及更高版本中。

其他回答

如果由于某些原因不能(或不想)使用控制台/终端,还有另一种解决方案。您可以让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());

Visualvm跟踪:

如果你不能从jvisualvm连接到你正在运行的JVM,因为你没有使用正确的JVM参数启动它(并且它在远程框上),在远程框上运行jstatd,然后,假设你有一个直接连接,在visualvm中将它添加为“远程主机”,双击主机名,该框上的所有其他JVM将神奇地显示在visualvm中。

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

一旦你可以看到你想要的进程,在jvisualvm中钻到它,并使用monitor选项卡-> "heapdump"按钮。

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

为了从windows中的子java进程中获取线程转储/堆转储,首先需要确定子进程Id。

通过发出命令:jps,您将能够获得在您的windows计算机上运行的所有java进程id。您需要从该列表中选择子进程Id。一旦有了子进程Id,就可以使用各种选项来捕获线程转储和堆转储。

捕获线程转储:

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

jstack 杀了3 jvisualVM 江铃汽车 Windows (Ctrl + Break) ThreadMXBean APM的工具 jcmd

关于每个选项的详细信息可以在本文中找到。有了捕获线程转储之后,可以使用fastThread、samurai等工具来分析线程转储。

捕获堆转储:

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

jmap - xx: + HeapDumpOnOutOfMemoryError jcmd JVisualVM JMX 编程方法 管理控制台

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

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

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

jstack和jps都包含在Sun JDK版本6及更高版本中。