私はコンソールから実行するJavaアプリケーションを持っていて、それが今度は別のJavaプロセスを実行します。その子プロセスのスレッド/ヒープダンプを取得したいのですが。
Unixではkill -3 <pid>
を実行できますが、Windowsでは、コンソールでCtrl + Breakを押すことがスレッドダンプを取得する唯一の方法です。しかし、それは私に親プロセスのダンプを与えるだけで、子は与えません。
そのヒープダンプを取得する別の方法はありますか?
jmap
を知っていれば、実行中のプロセスのダンプを取得するためにpid
を使用できます。
pid
を取得するには、タスクマネージャまたはリソースモニタを使用してください。それから
jmap -dump:format=b,file=cheap.bin <pid>
そのプロセスのヒープを取得します。
あなたは2つの異なるJavaダンプを混同しています。 kill -3
は、ヒープダンプではなくスレッドダンプを生成します。
スレッドダンプ=テキストとして標準出力に出力されるJVM内の各スレッドのスタックトレース。
ヒープダンプ= JVMプロセスがバイナリファイルに出力するためのメモリの内容。
Windowsでスレッドダンプを取るには、 CTRL+BREAK JVMがフォアグラウンドプロセスの場合は、最も簡単な方法です。 CygwinやMobaXtermのようにWindows上にunix風のシェルがある場合は、Unixの場合と同様にkill -3 {pid}
を使用できます。
Unixでスレッドダンプをとるには、 CTRL+C あなたのJVMがフォアグラウンドプロセスであるか、あなたがJVMのための正しいPIDを取得する限りkill -3 {pid}
が動作するならば。
どちらのプラットフォームでも、Javaには役立ついくつかのユーティリティが付属しています。スレッドダンプの場合、jstack {pid}
が最善の策です。 http://docs.Oracle.com/javase/1.5.0/docs/tooldocs/share/jstack.html
ダンプの問題を解決するためだけに、ヒープダンプは解釈が難しいため一般的には使用されません。しかし、それらをどこでどのように見ればよいのかを知っていれば、それらにはたくさんの役に立つ情報があります。最も一般的な使い方は、メモリリークを見つけることです。 OutOfMemoryError、-D
に基づいてヒープダンプが自動的に生成されるように、Javaコマンドラインで-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}
です。
上記のjconsole/visualvmを使用する以外に、別のコマンドラインウィンドウでjstack -l <vm-id>
を使用してその出力を取得することができます。
<vm-id>は、タスクマネージャ(WindowsおよびUNIXではプロセスID)を使用するか、jps
を使用して見つけることができます。
jstack
とjps
はどちらもSun JDKバージョン6以降に含まれています。
JDK(jvisualvm.exe)と一緒に配布されているJava VisualVMをお勧めします。動的に接続し、スレッドとヒープにアクセスできます。私はいくつかの問題に非常に貴重であるとわかりました。
以下のいずれかの方法を試してください。
32ビットJVMの場合:
jmap -dump:format=b,file=<heap_dump_filename> <pid>
64ビットJVM(明示的に引用符付き)の場合
jmap -J-d64 -dump:format=b,file=<heap_dump_filename> <pid>
VMパラメーターにG1GCアルゴリズムを含む64ビットJVMの場合(ライブオブジェクトヒープのみがG1GCアルゴリズムで生成されます)
jmap -J-d64 -dump:live,format=b,file=<heap_dump_filename> <pid>
関連SE質問: jmapコマンドでのJavaヒープダンプエラー:時期尚早EOF
Server-jre 8以降であれば、これを使うことができます。
jcmd PID GC.heap_dump /tmp/dump
メモリ不足のヒープダンプが必要な場合は、オプション-XX:-HeapDumpOnOutOfMemoryError
を付けてJavaを起動できます。
jconsole
(Java 6のSDKに含まれています)を実行してから、Javaアプリケーションに接続することができます。実行中のすべてのスレッドとそのスタックトレースが表示されます。
あなたはCygwinからkill -3 <pid>
を送ることができます。あなたはWindowsプロセスを見つけるためにCygwinのps
オプションを使用しなければなりませんそしてそれからそのプロセスに信号を送るだけです。
あなたは2番目のJava実行ファイルからあるファイルに出力をリダイレクトしなければなりません。次に、 SendSignal を使用して 2番目のプロセスに "-3" を送信します。
JDK 1.6以上を使用している場合は、jmap
コマンドを使用してJavaプロセスのヒープダンプを取得できます。条件はProcessIDを知っておく必要があります。
Windowsマシンを使用している場合は、タスクマネージャを使用してPIDを取得できます。 Linuxマシンの場合は、アプリケーションに応じて、ps -A | grep Java
、netstat -tupln | grep Java
、top | grep Java
などのさまざまなコマンドを使用できます。
それから、1234がPIDであるjmap -dump:format=b,file=sample_heap_dump.hprof 1234
のようなコマンドを使用できます。
Hprofファイルを解釈するために利用可能な さまざまなツール があります。使いやすいOracleのvisualvmツールをお勧めします。
私は、 PsExec
とjcmd
を使用して、 jvmdump.bat
というJava 8用の小さなバッチスクリプトを書きました。これは、スレッド、ヒープ、システムをダンプします。プロパティ、および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
スクリプトを実行できます。
何らかの理由でコンソール/ターミナルを使用できない(または使用したくない)場合は、別の解決策があります。 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フォローアップ:
正しいJVM引数で起動しなかったためにjvisualvmから実行中のJVMに「接続できない」場合(およびリモートボックス上にある場合)、リモートボックス上でjstatd
を実行します。直接接続していると仮定します。それをvisualvmの「リモートホスト」として追加し、ホスト名をダブルクリックすると、そのボックス上の他のすべてのJVMが魔法のようにvisualvmに表示されます。
そのボックスのポートに「直接接続」していない場合は、 プロキシ を通してこれを行うこともできます。
あなたが望むプロセスを見ることができたら、jvisualvmでそれにドリルインして、モニタータブ - >「ヒープダンプ」ボタンを使ってください。
多分 jcmd ?
Jcmdユーティリティーは、診断コマンド要求をJVMに送信するために使用されます。これらの要求は、Javaフライトレコーディングの制御、トラブルシューティング、およびJVMとJavaアプリケーションの診断に役立ちます。
Jcmdツールは、OracleのJava 7で導入され、JVMプロセスでJavaプロセスのIDの識別(jpsに似たもの)、ヒープダンプの取得(jmapに似たもの)、スレッドダンプの取得(jstackに似たもの)を使うことで特に役立ちます。システムプロパティやコマンドラインフラグなどの仮想マシンの特性の表示(jinfoに似ています)、ガベージコレクション統計の取得(jstatに似ています)。 jcmdツールは、「JVMアプリケーションの問題を調査して解決するためのスイス軍のナイフ」および「隠された宝石」と呼ばれています。
これがjcmd
を呼び出す際に使用する必要があるプロセスです。
jcmd <pid> GC.heap_dump <file-path>
に行くJavaヒープダンプ を取ることについてのより多くの情報のためにそれをチェックしてください。
以下の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;
}
}