Programmatic way to get address shown for Java monitors in a stack dump?
Asked Answered
T

3

6

If you get the stack dump of a process, e.g., via jstack, you get information about locked monitors (and synchronizers) with an address for each. E.g., from a trivially deadlocked two-thread process (using 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)

Is there any way to get, at runtime in the Java code, the same addresses shown above, such as 0x00000007c14e6368?

I've tried using the identity hash code on the objects the monitor correspond to, as well as MonitorInfo via ThreadMXBean, with no luck (the values don't correspond, at least on 64-bit java).

Tolerate answered 30/10, 2012 at 0:54 Comment(0)
O
4

I suppose, there is no easy way to get an address of a monitor. Here is how jstack does it

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);
    }

}

To run this snippet don't forget to add $JDK_HOME/lib/tools.jar to the classpath.

Here is the output it produces 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

...

Let's look closer at what hsvm.remoteDataDump("-l") does

...

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");
    }
}
...

and here is an implementation of the execute method for windows (you can find it in 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; 

So basically the named pipe is opened and some command is executed over it and all the magic is in the native code in 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;
}

Generally speaking if you would like to extract the address of the object you've obtained the monitor of, you can parse the output of the very first snippet and extract the necessary fragment, for example, by thread id.

Other options are attaching to your process in the debug mode and using debugger api or JNI.

Osullivan answered 31/10, 2012 at 5:17 Comment(0)
N
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.

Neely answered 31/10, 2012 at 17:49 Comment(0)
N
-2

So, your question is actually, is there some way to determine which two objects are deadlocked in a program? This trivial example actually points out a way to check for deadlock. One of the most common causes of deadlock is retrieving multiple locks, but in two different orders. So, search the code where it locks to ensure you are always requesting the locks in the same order.

As an aside, the fact that you are requiring multiple locks to perform some operation means that you should change the design to only require a single lock for any operation.

Neely answered 30/10, 2012 at 5:15 Comment(3)
No, that's not my question. The question is that I have a process which deadlocks say every 100,000 iterations in production, and I would like to find out which objects were actually involved in the deadlock, perhaps by logging monitor addresses at some point. Change the design to only require a single lock for any operation? lol.Tolerate
Not a single lock for all operations, but a single lock for any single operation. I think a code review would actually find the problem. This sounds exactly like what I just described.Neely
@Toby I still disagree. He's trying to find the objects involved in a deadlock by their addresses in the jstack dump. Which is a convoluted way of determining the object instances involved in the deadlock. I was suggesting trying to solve the issue at a higher level.Neely

© 2022 - 2024 — McMap. All rights reserved.