web-dev-qa-db-ja.com

コンソールで実行されていないWindows上でJavaプロセスのスレッドとヒープダンプを取得する方法

私はコンソールから実行するJavaアプリケーションを持っていて、それが今度は別のJavaプロセスを実行します。その子プロセスのスレッド/ヒープダンプを取得したいのですが。

Unixではkill -3 <pid>を実行できますが、Windowsでは、コンソールでCtrl + Breakを押すことがスレッドダンプを取得する唯一の方法です。しかし、それは私に親プロセスのダンプを与えるだけで、子は与えません。

そのヒープダンプを取得する別の方法はありますか?

219
macgreg

jmapを知っていれば、実行中のプロセスのダンプを取得するためにpidを使用できます。

pidを取得するには、タスクマネージャまたはリソースモニタを使用してください。それから

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

そのプロセスのヒープを取得します。

359
rkaganda

あなたは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/

110
Derek

Linuxプロセスで.hprofファイルを作成する最良の方法は、jmapコマンドを使用することです。例えば、jmap -dump:format=b,file=filename.hprof {PID}です。

30
Roberto Flores

上記のjconsole/visualvmを使用する以外に、別のコマンドラインウィンドウでjstack -l <vm-id>を使用してその出力を取得することができます。

<vm-id>は、タスクマネージャ(WindowsおよびUNIXではプロセスID)を使用するか、jpsを使用して見つけることができます。

jstackjpsはどちらもSun JDKバージョン6以降に含まれています。

18
ankon

JDK(jvisualvm.exe)と一緒に配布されているJava VisualVMをお勧めします。動的に接続し、スレッドとヒープにアクセスできます。私はいくつかの問題に非常に貴重であるとわかりました。

16
Lawrence Dol

以下のいずれかの方法を試してください。

  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のさまざまなオプションを見てください

14
Ravindra babu

Server-jre 8以降であれば、これを使うことができます。

jcmd PID GC.heap_dump /tmp/dump
13
Atul Soman

メモリ不足のヒープダンプが必要な場合は、オプション-XX:-HeapDumpOnOutOfMemoryErrorを付けてJavaを起動できます。

c.f JVMオプションのリファレンスページ

12

jconsole(Java 6のSDKに含まれています)を実行してから、Javaアプリケーションに接続することができます。実行中のすべてのスレッドとそのスタックトレースが表示されます。

6
Steve Kuo

あなたはCygwinからkill -3 <pid>を送ることができます。あなたはWindowsプロセスを見つけるためにCygwinのpsオプションを使用しなければなりませんそしてそれからそのプロセスに信号を送るだけです。

5
krosenvold

あなたは2番目のJava実行ファイルからあるファイルに出力をリダイレクトしなければなりません。次に、 SendSignal を使用して 2番目のプロセスに "-3" を送信します。

4
Yoni Roit

JDK 1.6以上を使用している場合は、jmapコマンドを使用してJavaプロセスのヒープダンプを取得できます。条件はProcessIDを知っておく必要があります。

Windowsマシンを使用している場合は、タスクマネージャを使用してPIDを取得できます。 Linuxマシンの場合は、アプリケーションに応じて、ps -A | grep Javanetstat -tupln | grep Javatop | grep Javaなどのさまざまなコマンドを使用できます。

それから、1234がPIDであるjmap -dump:format=b,file=sample_heap_dump.hprof 1234のようなコマンドを使用できます。

Hprofファイルを解釈するために利用可能な さまざまなツール があります。使いやすいOracleのvisualvmツールをお勧めします。

3
Badal

私は、 PsExecjcmdを使用して、 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スクリプトを実行できます。

2
isapir

何らかの理由でコンソール/ターミナルを使用できない(または使用したくない)場合は、別の解決策があります。 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());
1
HugoTeixeira

Visualvmフォローアップ:

正しいJVM引数で起動しなかったためにjvisualvmから実行中のJVMに「接続できない」場合(およびリモートボックス上にある場合)、リモートボックス上でjstatdを実行します。直接接続していると仮定します。それをvisualvmの「リモートホスト」として追加し、ホスト名をダブルクリックすると、そのボックス上の他のすべてのJVMが魔法のようにvisualvmに表示されます。

そのボックスのポートに「直接接続」していない場合は、 プロキシ を通してこれを行うこともできます。

あなたが望むプロセスを見ることができたら、jvisualvmでそれにドリルインして、モニタータブ - >「ヒープダンプ」ボタンを使ってください。

0
rogerdpack

多分 jcmd

Jcmdユーティリティーは、診断コマンド要求をJVMに送信するために使用されます。これらの要求は、Javaフライトレコーディングの制御、トラブルシューティング、およびJVMとJavaアプリケーションの診断に役立ちます。

Jcmdツールは、OracleのJava 7で導入され、JVMプロセスでJavaプロセスのIDの識別(jpsに似たもの)、ヒープダンプの取得(jmapに似たもの)、スレッドダンプの取得(jstackに似たもの)を使うことで特に役立ちます。システムプロパティやコマンドラインフラグなどの仮想マシンの特性の表示(jinfoに似ています)、ガベージコレクション統計の取得(jstatに似ています)。 jcmdツールは、「JVMアプリケーションの問題を調査して解決するためのスイス軍のナイフ」および「隠された宝石」と呼ばれています。

これがjcmdを呼び出す際に使用する必要があるプロセスです。

  1. jcmd <pid> GC.heap_dump <file-path>に行く
  2. その中で
  3. pid:はヒープダンプがキャプチャされるJavaプロセスIDです。
  4. file-path:ヒープダンプが印刷されるファイルパスです。

Javaヒープダンプ を取ることについてのより多くの情報のためにそれをチェックしてください。

0
Stas

以下の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