获取堆栈转储中Java监视器的地址的程序化方法



如果您获得了一个进程的堆栈转储,例如,通过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

我已经尝试在对象上使用标识哈希代码,并且通过 ThreadMXBean对应的对象以及MonitorInfo,没有运气(值至少在64位Java上不通信)。

我想,没有简单的方法来获取监视器的地址。这是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添加到ClassPath。

这是它产生的输出 2012-10-31 08:48:08 完整线转储Java热点(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的执行方法的实现(您可以在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。

也许您可以记录请求锁定的对象。然后,您会知道谁在僵局时有锁。

那么,您的问题实际上是有什么方法可以确定程序中的两个对象?这个琐碎的例子实际上指出了检查僵局的一种方法。僵局最常见的原因之一是检索多个锁,但有两个不同的订单。因此,搜索锁定的代码以确保您始终以相同的顺序请求锁。

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