如何在Windows上获得Java进程的线程和堆转储's不运行在控制台

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

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

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

576068 次浏览

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

你必须重定向输出从第二个java可执行文件到一些文件。 然后,对第二个进程使用SendSignal把“3”

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

我推荐JDK附带的Java VisualVM (jvisualvm.exe)。它可以动态连接并访问线程和堆。我发现在某些问题上它是无价的。

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

& lt; vm-id>可以使用任务管理器(在windows和unix上是进程id)或使用jps找到。

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

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

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

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

获取该进程的堆。

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

jmap -dump:format=b,file=heap.hprof $(pgrep java)

如果你想要内存不足的堆转储,你可以使用选项-XX:-HeapDumpOnOutOfMemoryError启动Java

JVM选项参考页

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

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

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

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

要在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

先把转储问题讲完:堆转储不常用,因为它们很难解释。但是,如果你知道在哪里/如何看它们,它们有很多有用的信息。最常见的用法是定位内存泄漏。在java命令行上设置-D是一个很好的实践,以便在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/

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

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

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

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

有各种工具可用来解释hprof文件。我将推荐使用简单的Oracle的visualvm工具。

如果你在服务器-jre 8及以上,你可以使用这个:

jcmd PID GC.heap_dump /tmp/dump

试试下面的一种选择。

  1. 对于32位JVM:

    jmap -dump:format=b,file=<heap_dump_filename> <pid>
    
  2. For 64 bit JVM (explicitly quoting):

    jmap -J-d64 -dump:format=b,file=<heap_dump_filename> <pid>
    
  3. For 64 bit JVM with G1GC algorithm in VM parameters (Only live objects heap is generated with G1GC algorithm):

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

Related SE question: Java heap dump error with jmap command : Premature EOF

Have a look at various options of jmap at this article

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

我为Java 8写了一个名为jvmdump.bat的小批处理脚本(使用PsExecjcmd),它转储线程、堆、系统属性和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脚本。

在Oracle JDK中,我们有一个名为jmap的命令(在Java Home的bin文件夹中可用)。 命令用法:

.使用实例

Jmap (option) (pid)

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

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

下面的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;
}

也许jcmd ?

Jcmd实用程序用于发送诊断命令请求到JVM,其中这些请求对于控制Java飞行记录,故障排除和诊断JVM和Java应用程序是有用的。

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

下面是调用jcmd时需要使用的过程:

  1. jcmd <pid> GC.heap_dump <file-path>
  2. 在这
  3. pid: Java进程Id,将为其捕获堆转储
  4. file-path:打印堆转储的文件路径。

查看更多关于Java堆转储的信息。

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

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

捕获线程转储:

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

  1. jstack
  2. 杀了3
  3. jvisualVM
  4. 江铃汽车
  5. Windows (Ctrl + Break)
  6. ThreadMXBean
  7. APM的工具
  8. jcmd

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

捕获堆转储:

有7个选项来捕获堆转储:

  1. < p > jmap < br >

  2. < p > - xx: + HeapDumpOnOutOfMemoryError < br >

  3. < p > jcmd < br >

  4. < p > JVisualVM < br >

  5. < p > JMX < br >

  6. < br > < p >编程方法

  7. < br > < p >管理控制台

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

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

执行“jcmd”命令可获取java应用程序的进程id。

如何获得线程转储?

jcmd PID线程。打印> thread.dump

参考链接

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

如何获得堆转储?

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

. dump

PID:应用程序的进程号。参考链接