如何在未在控制台中运行的Windows上获取Java进程的线程和堆转储


232

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

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

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


Answers:


376

jmap假设您知道,则可以使用来获取正在运行的任何进程的转储pid

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

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

获得该进程的堆。


jmap在Windows中不适用于JDK5。有什么方法可以在Windows上使用JDK5进行转储吗?
Santron Manibharathi

173
这个线程变得如此流行,以至于我刚刚听到有人将堆转储称为“ cheap.bin”
mjaggard 2015年

7
更直接的文件名:“ heap.hprof”,它是HPROF格式。
米高梅

1
确保使用正确的用户启动了Java进程。在我的情况下,它是tomcat8 ps -C java -o pid sudo -u tomcat8 jmap -dump:format = b,file = <文件名> <pid>
bitsabhi

115

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

线程转储= JVM输出到stdout的每个线程的堆栈跟踪作为文本。

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

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

要在Unix中进行线程转储,如果您的JVM是前台进程,则CTRL+Ckill -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/


3
在我的linux Ctrl-C中断(终止)它上,我做Ctrl- \
nafg 2014年

考虑和它的一般影响“采取在Windows线程转储,CTRL + BREAK”。它实际上取决于制造商的工程决策。FE,Lenova,IIRC是cntrl + fn + p。
ChiefTwoPencils 2015年


19

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

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

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


Java 1.6不支持这些工具。Java 1.6仅具有jconsole。
Vanchinathan Chandrasekaran 2011年

7
您可能会混淆JDK和JRE,我明确提到过JDK。查看文档的工具:download.oracle.com/javase/6/docs/technotes/tools/share/...download.oracle.com/javase/6/docs/technotes/tools/share/...
Ankon酒店

17

我建议与JDK(jvisualvm.exe)一起发布的Java VisualVM。它可以动态连接并访问线程和堆。我发现某些问题无价之宝。


2
在大多数情况下,这是不可行的,因为它附带了开销,并且通常从生产机中检索线程转储。
哈马德·达尔

最初的问题是关于“非运行”过程的。jvisualvm可能无法连接。
Jaberino '16

3
@Jaberino:不,这是关于Windows中当前正在运行的Java进程,没有与之关联的控制台。
劳伦斯·多尔

在最新的Java版本中,JMC / JFR取代了Java VisualVM 。另请参见JVisualVM和Java Mission Control之间有什么区别?
Vadzim


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

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

相关的SE问题:jmap命令的Java堆转储错误:过早的EOF

看看各种选项jmap,在此文章


13

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

cf JVM选项参考页面


谢谢丹尼尔。在Windows机器上在哪里创建此文件?有默认路径吗?
熔岩

1
@lava您可以通过-XX:HeapDumpPath设置路径,如Oracle的VM Options页面中所述。
kamczak

太棒了 我想通宵进行测试,以期显示内存泄漏,但我不在时担心OOM和崩溃。太棒了。
巴兹尔(Basil)'02

7

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


迄今为止最好的答案!直到现在都不知道,这确实很实用!
Xerus

7

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



3

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

如果您在Windows计算机上,则可以使用任务管理器获取PID。对于Linux机器,您可以使用各种命令,例如ps -A | grep javanetstat -tupln | grep javatop | grep java,这取决于您的应用程序。

然后,您可以使用命令,例如jmap -dump:format=b,file=sample_heap_dump.hprof 12341234是PID。

有多种工具可用于解释hprof文件。我将推荐Oracle的visualvm工具,该工具易于使用。


3

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

3

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

我为Java 8(使用PsExecjcmd)编写了一个小的批处理脚本,名为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%

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

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

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


2

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

执行命令“ jcmd”以获取Java应用程序的进程ID。

如何获得线程转储?

jcmd PID Thread.print>线程。转储

参考链接

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

如何获得堆转储?

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

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


1

也许jcmd

Jcmd实用程序用于将诊断命令请求发送到JVM,这些请求对于控制Java飞行记录,进行故障排除以及诊断JVM和Java应用程序很有用。

jcmd工具是随Oracle Java 7引入的,对于通过使用Java标识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. file-path:是在其中打印堆转储的文件路径。

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


0

Visualvm跟进:

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

如果您没有与该盒子上端口的“直接连接”,也可以通过proxy进行

一旦看到所需的过程,请在jvisualvm中进行深入研究,并使用Monitor选项卡->“ heapdump”按钮。


0

下面的Java代码用于通过提供PID来获取Java进程的堆转储。本程序使用远程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

为了从Windows中的子Java进程中进行线程转储/堆转储,您需要将子进程ID标识为第一步。

通过发出命令:jps,您将能够获取Windows计算机上运行的所有Java进程ID。从此列表中,您需要选择子进程ID。拥有子进程ID后,可以使用各种选项来捕获线程转储和堆转储。

捕获线程转储:

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

  1. jstack
  2. 杀死-3
  3. jvisualVM
  4. 江铃汽车
  5. Windows(Ctrl + Break)
  6. 线程MXBean
  7. APM工具
  8. jcmd

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

捕获堆转储:

有7种捕获堆转储的选项:

  1. 映射

  2. -XX:+ HeapDumpOnOutOfMemoryError

  3. jcmd

  4. JVisualVM

  5. JMX

  6. 程序化方法

  7. 管理控制台

有关每个选项的详细信息,请参见本文。捕获堆转储后,可以使用Eclipse Memory Analysis工具HeapHero工具来分析捕获的堆转储。


-1

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

jmap(选项)(pid)

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.