5

如果您获得进程的堆栈转储,例如,通过 jstack,您将获得有关锁定监视器(和同步器)的信息,每个监视器都有一个地址。例如,来自一个简单死锁的双线程进程(使用 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

我已经尝试在监视器对应的对象以及MonitorInfovia上使用身份哈希码,但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 Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.5-b03 mixed mode):

"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

Perhaps you can log the object that requests a lock. Then you would know who has the lock at the time of the deadlock.

于 2012-10-31T17:49:19.363 回答
-2

所以,你的问题实际上是,有没有办法确定程序中哪两个对象死锁了?这个简单的例子实际上指出了一种检查死锁的方法。死锁最常见的原因之一是检索多个锁,但顺序不同。因此,请搜索它锁定的代码,以确保您始终以相同的顺序请求锁定。

顺便说一句,您需要多个锁来执行某些操作这一事实意味着您应该将设计更改为只需要一个锁来执行任何操作。

于 2012-10-30T05:15:57.563 回答