5

たとえば jstack を介してプロセスのスタック ダンプを取得すると、ロックされたモニター (およびシンクロナイザー) に関する情報と、それぞれのアドレスが取得されます。たとえば、自明にデッドロックされた 2 スレッド プロセスから (jstack を使用):

"Thread-0" prio=10 tid=0x00007f1444042000 nid=0x2818 waiting for monitor entry [0x00007f14433ca000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at scrap.DeadlockTest$Deadlocker.run(DeadlockTest.java:49)
    - waiting to lock <0x00000007c14e6378> (a java.lang.Object)
    - locked <0x00000007c14e6368> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:619)

... (omitted some lines here)

Java stack information for the threads listed above:
===================================================
"Thread-1":
    at scrap.DeadlockTest$Deadlocker.run(DeadlockTest.java:49)
    - waiting to lock <0x00000007c14e6368> (a java.lang.Object)
    - locked <0x00000007c14e6378> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:619)
"Thread-0":
    at scrap.DeadlockTest$Deadlocker.run(DeadlockTest.java:49)
    - waiting to lock <0x00000007c14e6378> (a java.lang.Object)
    - locked <0x00000007c14e6368> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:619)

Javaコードで実行時に、上記と同じアドレスを取得する方法はあります0x00000007c14e6368か?

MonitorInfoモニターが対応するオブジェクトでIDハッシュコードを使用しようとしましたが、ThreadMXBean運がありませんでした(少なくとも64ビットJavaでは値が対応していません)。

4

3 に答える 3

3

モニターのアドレスを取得する簡単な方法はないと思います。jstackがそれを行う方法は次のとおりです

import com.sun.tools.attach.VirtualMachine;
import sun.tools.attach.HotSpotVirtualMachine;

import java.io.InputStream;
import java.lang.management.ManagementFactory;

public class Main {

    public static void main(String[] args) throws Exception {
        VirtualMachine vm = VirtualMachine.attach(getPid());

        HotSpotVirtualMachine hsvm = (HotSpotVirtualMachine) vm;
        InputStream in = hsvm.remoteDataDump("-l");

        byte b[] = new byte[256];
        int n;
        do {
            n = in.read(b);
            if (n > 0) {
                String s = new String(b, 0, n, "UTF-8");
                System.out.print(s);
            }
        } while (n > 0);
        in.close();
    }

    private static String getPid() {
        String name = ManagementFactory.getRuntimeMXBean().getName();
        int ind = name.indexOf('@');
        return name.substring(0, ind);
    }

}

このスニペットを実行するには、忘れずに$JDK_HOME/lib/tools.jarクラスパスに追加してください。

2012-10-31 08:48:08 完全なスレッド ダンプ Java HotSpot(TM) 64 ビット サーバー VM (20.5-b03 混合モード) が生成する出力は次のとおりです。

"Monitor Ctrl-Break" daemon prio=6 tid=0x0000000006b98000 nid=0x1d70 runnable [0x00000000074df000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.PlainSocketImpl.accept(PlainSocketImpl.java:408)
    - locked <0x00000007d5d53148> (a java.net.SocksSocketImpl)
    at java.net.ServerSocket.implAccept(ServerSocket.java:462)
    at java.net.ServerSocket.accept(ServerSocket.java:430)
    at com.intellij.rt.execution.application.AppMain$1.run(AppMain.java:82)
    at java.lang.Thread.run(Thread.java:662)

   Locked ownable synchronizers:
    - None

...

hsvm.remoteDataDump("-l")何ができるかを詳しく見てみましょう

...

public InputStream remoteDataDump(Object ... args) throws IOException {
    return executeCommand("threaddump", args);
}

/*
 * Execute the given command in the target VM - specific platform
 * implementation must implement this.
 */
abstract InputStream execute(String cmd, Object ... args)
    throws AgentLoadException, IOException;

/*
 * Convenience method for simple commands 
 */
private InputStream executeCommand(String cmd, Object ... args) throws IOException {
    try {
        return execute(cmd, args);
    } catch (AgentLoadException x) {
        throw new InternalError("Should not get here");
    }
}
...

これは、Windows 用の execute メソッドの実装です ( で見つけることができますsun.tools.attach.WindowsVirtualMachine) 。

InputStream execute(String cmd, Object ... args) 
        throws AgentLoadException, IOException {

    assert args.length <= 3;        // includes null

    // create a pipe using a random name
    int r = (new Random()).nextInt();
    String pipename = "\\\\.\\pipe\\javatool" + r; 
    long hPipe = createPipe(pipename);

    // check if we are detached - in theory it's possible that detach is invoked 
    // after this check but before we enqueue the command.
    if (hProcess == -1) { 
        closePipe(hPipe);
        throw new IOException("Detached from target VM");
    }

    try {
        // enqueue the command to the process
        enqueue(hProcess, stub, cmd, pipename, args);

        // wait for command to complete - process will connect with the
        // completion status
        connectPipe(hPipe);

        // create an input stream for the pipe
        PipedInputStream is = new PipedInputStream(hPipe);

        // read completion status
        int status = readInt(is);
        if (status != 0) {
            // special case the load command so that the right exception is thrown
            if (cmd.equals("load")) {
                throw new AgentLoadException("Failed to load agent library");
            } else {
                throw new IOException("Command failed in target VM");
            }
         }      

        // return the input stream
        return is;

    } catch (IOException ioe) {
        closePipe(hPipe);
        throw ioe;
    }
} 

static native void init();

static native byte[] generateStub();

static native long openProcess(int pid) throws IOException;

static native void closeProcess(long hProcess) throws IOException;

static native long createPipe(String name) throws IOException;

static native void closePipe(long hPipe) throws IOException;

static native void connectPipe(long hPipe) throws IOException;    

static native int readPipe(long hPipe, byte buf[], int off, int buflen) throws IOException;

static native void enqueue(long hProcess, byte[] stub,
    String cmd, String pipename, Object ... args) throws IOException; 

したがって、基本的に名前付きパイプが開かれ、いくつかのコマンドがその上で実行され、すべての魔法はネイティブコードの中にありますhotspot/src/share/vm/services/attachListener.cpp

// Implementation of "threaddump" command - essentially a remote ctrl-break
//
static jint thread_dump(AttachOperation* op, outputStream* out) {
    bool print_concurrent_locks = false;
    if (op->arg(0) != NULL && strcmp(op->arg(0), "-l") == 0) {
        print_concurrent_locks = true;
    }

    // thread stacks
    VM_PrintThreads op1(out, print_concurrent_locks);
    VMThread::execute(&op1);

    // JNI global handles
    VM_PrintJNI op2(out);
    VMThread::execute(&op2);

    // Deadlock detection
    VM_FindDeadlocks op3(out);
    VMThread::execute(&op3);

    return JNI_OK;
}

一般的に言えば、モニターを取得したオブジェクトのアドレスを抽出したい場合は、最初のスニペットの出力を解析し、スレッド ID などによって必要なフラグメントを抽出できます。

その他のオプションは、デバッグ モードでプロセスにアタッチし、デバッガー API または JNI を使用することです。

于 2012-10-31T05:17:44.077 に答える
0

おそらく、ロックを要求するオブジェクトをログに記録できます。そうすれば、デッドロックの時点で誰がロックを持っているかがわかります。

于 2012-10-31T17:49:19.363 に答える
-2

それで、あなたの質問は実際には、プログラムでデッドロックされている2つのオブジェクトを特定する方法はありますか? この簡単な例は、実際にデッドロックをチェックする方法を示しています。デッドロックの最も一般的な原因の 1 つは、2 つの異なる順序で複数のロックを取得することです。そのため、ロックするコードを検索して、常に同じ順序でロックを要求していることを確認してください。

余談ですが、ある操作を実行するために複数のロックが必要であるということは、どの操作でも 1 つのロックのみを必要とするように設計を変更する必要があることを意味します。

于 2012-10-30T05:15:57.563 に答える