调试监视器锁

为了更深入地理解监视器锁,本文使用gdb调试Hotspot虚拟机展示监视器锁获取与释放的部分执行过程。

调试准备

为了获得Thread类对象的线程ID,内核线程ID与Linux线程ID这三者之间的对应关系,我在hotspot/src/share/vm/runtime/thread.cpp的JavaThread::run函数起始处增加了一行代码,可以按顺序输出JVM中的线程指针、Thread类对象的线程ID、该对象对应的内核线程ID和Linux线程ID。

fprintf(stderr, "JavaThread address: %lx, thread id in Java: %d, LWP: %d, pthread tid: %lx\n", this, java_lang_Thread::thread_id(this->threadObj()), this->_osthread->thread_id(), this->_osthread->pthread_id());

获取监视器锁

获取监视器锁的实验代码如下:

public class MonitorEnter extends Thread {
    private final Object lock;

    public MonitorEnter(Object lock) {
        this.lock = lock;
    }

    public void run() {
        synchronized (lock) {
            try {
                while (true) {
                    Thread.sleep(1000);
                    System.out.println("Thread name: " + Thread.currentThread().getName() + " id: " +
                            Thread.currentThread().getId());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("main thread id: " + Thread.currentThread().getId());
        Object lock = new Object();
        Thread t1 = new MonitorEnter(lock);
        t1.setName("t1");
        System.out.println("t1 id: " + t1.getId());
        Thread t2 = new MonitorEnter(lock);
        t2.setName("t2");
        System.out.println("t2 id: " + t2.getId());
        Thread t3 = new MonitorEnter(lock);
        t3.setName("t3");
        System.out.println("t3 id: " + t3.getId());
        Thread t4 = new MonitorEnter(lock);
        t4.setName("t4");
        System.out.println("t4 id: " + t4.getId());
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

gdb调试的交互过程如下:

[suntao@CentOS-VM ~]$ cd openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/bin/
[suntao@CentOS-VM bin]$ gdb ./java
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/suntao/openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java...done.
(gdb) b /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:546
No source file named /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (/home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:546) pending.
(gdb) r -cp /home/suntao/test/ MonitorEnter
Starting program: /home/suntao/openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/bin/./java -cp /home/suntao/test/ MonitorEnter
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff7feb700 (LWP 11644)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7feb700 (LWP 11644)]
0x00007fffe10002b4 in ?? ()
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.3.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) c
Continuing.
[New Thread 0x7ffff4475700 (LWP 11645)]
[New Thread 0x7ffff4374700 (LWP 11646)]
[New Thread 0x7fffde08b700 (LWP 11647)]
[New Thread 0x7fffddf8a700 (LWP 11648)]
JavaThread address: 7ffff0106000, thread id in Java: 2, LWP: 11648, pthread tid: 7fffddf8a700
[New Thread 0x7fffdde89700 (LWP 11649)]
JavaThread address: 7ffff010b800, thread id in Java: 3, LWP: 11649, pthread tid: 7fffdde89700
[New Thread 0x7fffddd88700 (LWP 11650)]
JavaThread address: 7ffff015b000, thread id in Java: 4, LWP: 11650, pthread tid: 7fffddd88700
[New Thread 0x7fffddc87700 (LWP 11651)]
JavaThread address: 7ffff015d000, thread id in Java: 5, LWP: 11651, pthread tid: 7fffddc87700
[New Thread 0x7fffddb86700 (LWP 11652)]
JavaThread address: 7ffff0160800, thread id in Java: 6, LWP: 11652, pthread tid: 7fffddb86700
[New Thread 0x7fffdda85700 (LWP 11653)]
[New Thread 0x7fffdd984700 (LWP 11654)]
JavaThread address: 7ffff016c000, thread id in Java: 7, LWP: 11653, pthread tid: 7fffdda85700
main thread id: 1
t1 id: 8
t2 id: 9
t3 id: 10
t4 id: 11
[New Thread 0x7fffdd883700 (LWP 11655)]
JavaThread address: 7ffff01a9000, thread id in Java: 8, LWP: 11655, pthread tid: 7fffdd883700
[New Thread 0x7fffdd782700 (LWP 11656)]
JavaThread address: 7ffff01ab000, thread id in Java: 9, LWP: 11656, pthread tid: 7fffdd782700
[Switching to Thread 0x7fffdd782700 (LWP 11656)]

Breakpoint 1, ObjectMonitor::EnterI (this=0x7fffc8006368, __the_thread__=0x7ffff01ab000)
    at /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:546
546     ObjectWaiter node(Self) ;
(gdb) p _owner
$1 = (void * volatile) 0x7fffdd882780
(gdb) p OwnerIsThread
$2 = 0
(gdb) p _cxq
$3 = (ObjectWaiter * volatile) 0x0
(gdb) p _EntryList
$4 = (ObjectWaiter * volatile) 0x0
(gdb) c
Continuing.
[New Thread 0x7fffdd681700 (LWP 11657)]
Thread name: t1 id: 8
JavaThread address: 7ffff01ad000, thread id in Java: 10, LWP: 11657, pthread tid: 7fffdd681700
[New Thread 0x7fffdd580700 (LWP 11658)]
JavaThread address: 7ffff01af800, thread id in Java: 11, LWP: 11658, pthread tid: 7fffdd580700
[Switching to Thread 0x7fffdd580700 (LWP 11658)]

Breakpoint 1, ObjectMonitor::EnterI (this=0x7fffc8006368, __the_thread__=0x7ffff01af800)
    at /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:546
546     ObjectWaiter node(Self) ;
(gdb) p _owner
$5 = (void * volatile) 0x7fffdd882780
(gdb) p OwnerIsThread
$6 = 0
(gdb) p _cxq
$7 = (ObjectWaiter * volatile) 0x7fffdd781480
(gdb) p _cxq->_thread->_osthread->_thread_id
$8 = 11656
(gdb) p _cxq->_next
$9 = (ObjectWaiter * volatile) 0x0
(gdb) c
Continuing.
Thread name: t1 id: 8
[Switching to Thread 0x7fffdd681700 (LWP 11657)]

Breakpoint 1, ObjectMonitor::EnterI (this=0x7fffc8006368, __the_thread__=0x7ffff01ad000)
    at /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:546
546     ObjectWaiter node(Self) ;
(gdb) p _owner
$10 = (void * volatile) 0x7fffdd882780
(gdb) p OwnerIsThread
$11 = 0
(gdb) p _cxq
$12 = (ObjectWaiter * volatile) 0x7fffdd57f380
(gdb) p _cxq->_thread->_osthread->_thread_id
$13 = 11658
(gdb) p _cxq->_next->_thread->_osthread->_thread_id
$14 = 11656
(gdb) p _cxq->_next
$15 = (ObjectWaiter * volatile) 0x7fffdd781480
(gdb) p _cxq->_next->_next
$16 = (ObjectWaiter * volatile) 0x0
(gdb) n
547     Self->_ParkEvent->reset() ;
(gdb)
Thread name: t1 id: 8
548     node._prev   = (ObjectWaiter *) 0xBAD ;
(gdb)
549     node.TState  = ObjectWaiter::TS_CXQ ;
(gdb)
557         node._next = nxt = _cxq ;
(gdb)
558         if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
(gdb)
593     if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
(gdb) p _cxq
$17 = (ObjectWaiter * volatile) 0x7fffdd680400
(gdb) p _cxq->_thread->_osthread->_thread_id
$18 = 11657
(gdb) p _cxq->_next
$19 = (ObjectWaiter * volatile) 0x7fffdd57f380
(gdb) p _cxq->_next->_thread->_osthread->_thread_id
$20 = 11658
(gdb) p _cxq->_next->_next
$21 = (ObjectWaiter * volatile) 0x7fffdd781480
(gdb) p _cxq->_next->_next->_thread->_osthread->_thread_id
$22 = 11656
(gdb) p _cxq->_next->_next->_next
$23 = (ObjectWaiter * volatile) 0x0
(gdb) c
Continuing.
Thread name: t1 id: 8
Thread name: t1 id: 8
Thread name: t1 id: 8
^C
Program received signal SIGINT, Interrupt.
[Switching to Thread 0x7ffff7fec740 (LWP 11640)]
0x00007ffff7bc7f47 in pthread_join () from /lib64/libpthread.so.0
(gdb)

上述实验创建的线程如下:

线程名称 Thread类的getId方法返回值 内核线程ID JVM中的线程指针
t1 8 11655 0x7ffff01a9000
t2 9 11656 0x7ffff01ab000
t3 10 11657 0x7ffff01ad000
t4 11 11658 0x7ffff01af800
  • 线程t1首先获得了监视器锁,由于是从轻量级锁膨胀成监视器锁,因此_owner是轻量级锁记录的指针,OwnerIsThread是0印证了这一点;
  • 注意看断点的参数,如ObjectMonitor::EnterI (this=0x7fffc8006368, __the_thread__=0x7ffff01ab000)__the_thread__变量正是t2线程,这是t2尝试获得监视器的过程,其他线程同理;
  • Java中的synchronized关键字(二)一文提到在EnterI函数中若自旋失败,则参数线程被包装成ObjectWaiter并加入_cxq队首,调试输出表明_cxq链表的形式是t3 -> t4 -> t2 -> NULL,这验证了该文论述的正确性。

释放监视器锁

释放监视器锁的实验代码如下:

public class MonitorExit extends Thread {
    private final Object lock;

    public MonitorExit(Object lock) {
        this.lock = lock;
    }

    public void run() {
        synchronized (lock) {
            try {
                Thread.sleep(2000);
                System.out.println("Thread name: " + Thread.currentThread().getName() + " id: " +
                        Thread.currentThread().getId());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        System.out.println("main thread id: " + Thread.currentThread().getId());
        Object lock = new Object();
        Thread t1 = new MonitorExit(lock);
        t1.setName("t1");
        System.out.println("t1 id: " + t1.getId());
        Thread t2 = new MonitorExit(lock);
        t2.setName("t2");
        System.out.println("t2 id: " + t2.getId());
        Thread t3 = new MonitorExit(lock);
        t3.setName("t3");
        System.out.println("t3 id: " + t3.getId());
        Thread t4 = new MonitorExit(lock);
        t4.setName("t4");
        System.out.println("t4 id: " + t4.getId());
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

gdb调试的交互过程如下:

[suntao@CentOS-VM ~]$ cd openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/bin/
[suntao@CentOS-VM bin]$ gdb ./java
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-114.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/suntao/openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/bin/java...done.
(gdb) b /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:958
No source file named /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp.
Make breakpoint pending on future shared library load? (y or [n]) y

Breakpoint 1 (/home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:958) pending.
(gdb) r -cp /home/suntao/test/ MonitorExit
Starting program: /home/suntao/openjdk8/build/linux-x86_64-normal-server-slowdebug/jdk/bin/./java -cp /home/suntao/test/ MonitorExit
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
[New Thread 0x7ffff7feb700 (LWP 11705)]

Program received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7ffff7feb700 (LWP 11705)]
0x00007fffe10002b4 in ?? ()
Missing separate debuginfos, use: debuginfo-install glibc-2.17-260.el7_6.3.x86_64 libgcc-4.8.5-36.el7.x86_64 libstdc++-4.8.5-36.el7.x86_64
(gdb) c
Continuing.
[New Thread 0x7ffff4475700 (LWP 11706)]
[New Thread 0x7ffff4374700 (LWP 11707)]
[New Thread 0x7fffde08b700 (LWP 11708)]
[New Thread 0x7fffddf8a700 (LWP 11709)]
JavaThread address: 7ffff0106000, thread id in Java: 2, LWP: 11709, pthread tid: 7fffddf8a700
[New Thread 0x7fffdde89700 (LWP 11710)]
JavaThread address: 7ffff010b800, thread id in Java: 3, LWP: 11710, pthread tid: 7fffdde89700
[New Thread 0x7fffddd88700 (LWP 11711)]
JavaThread address: 7ffff015b000, thread id in Java: 4, LWP: 11711, pthread tid: 7fffddd88700
[New Thread 0x7fffddc87700 (LWP 11712)]
JavaThread address: 7ffff015d000, thread id in Java: 5, LWP: 11712, pthread tid: 7fffddc87700
[New Thread 0x7fffddb86700 (LWP 11713)]
JavaThread address: 7ffff0160800, thread id in Java: 6, LWP: 11713, pthread tid: 7fffddb86700
[New Thread 0x7fffdda85700 (LWP 11714)]
JavaThread address: 7ffff018d000, thread id in Java: 7, LWP: 11714, pthread tid: 7fffdda85700
[New Thread 0x7fffdd984700 (LWP 11715)]
main thread id: 1
t1 id: 8
t2 id: 9
t3 id: 10
t4 id: 11
[New Thread 0x7fffdd883700 (LWP 11716)]
JavaThread address: 7ffff01b9000, thread id in Java: 8, LWP: 11716, pthread tid: 7fffdd883700
[New Thread 0x7fffdd782700 (LWP 11717)]
JavaThread address: 7ffff01bb000, thread id in Java: 9, LWP: 11717, pthread tid: 7fffdd782700
[New Thread 0x7fffdd681700 (LWP 11718)]
JavaThread address: 7ffff01bd000, thread id in Java: 10, LWP: 11718, pthread tid: 7fffdd681700
[New Thread 0x7fffdd580700 (LWP 11719)]
JavaThread address: 7ffff01bf800, thread id in Java: 11, LWP: 11719, pthread tid: 7fffdd580700
Thread name: t1 id: 8
[Switching to Thread 0x7fffdd883700 (LWP 11716)]

Breakpoint 1, ObjectMonitor::exit (this=0x7fffc8002218, not_suspended=true, __the_thread__=0x7ffff01b9000)
    at /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:958
958    if (THREAD != _owner) {
(gdb) p _owner
$1 = (void * volatile) 0x7fffdd882500
(gdb) p OwnerIsThread
$2 = 0
(gdb) p _cxq
$3 = (ObjectWaiter * volatile) 0x7fffdd57f600
(gdb) p _cxq->_thread->_osthread->_thread_id
$4 = 11719
(gdb) p _cxq->_next
$5 = (ObjectWaiter * volatile) 0x7fffdd680580
(gdb) p _cxq->_next->_thread->_osthread->_thread_id
$6 = 11718
(gdb) p _cxq->_next->_next
$7 = (ObjectWaiter * volatile) 0x7fffdd781300
(gdb) p _cxq->_next->_next->_thread->_osthread->_thread_id
$8 = 11717
(gdb) p _cxq->_next->_next->_next
$9 = (ObjectWaiter * volatile) 0x0
(gdb) p _EntryList
$10 = (ObjectWaiter * volatile) 0x0
(gdb) n
959      if (THREAD->is_lock_owned((address) _owner)) {
(gdb)
964        assert (_recursions == 0, "invariant") ;
(gdb)
965        _owner = THREAD ;
(gdb)
966        _recursions = 0 ;
(gdb)
967        OwnerIsThread = 1 ;
(gdb) b 1103
Breakpoint 2 at 0x7ffff68e0b01: file /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp, line 1103.
(gdb) c
Continuing.

Breakpoint 2, ObjectMonitor::exit (this=0x7fffc8002218, not_suspended=true, __the_thread__=0x7ffff01b9000)
    at /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:1103
1103          if (QMode == 2 && _cxq != NULL) {
(gdb) p Knob_QMode
$11 = 0
(gdb) p _cxq
$12 = (ObjectWaiter * volatile) 0x7fffdd57f600
(gdb) p _EntryList
$13 = (ObjectWaiter * volatile) 0x0
(gdb) n
1114          if (QMode == 3 && _cxq != NULL) {
(gdb)
1152          if (QMode == 4 && _cxq != NULL) {
(gdb)
1187          w = _EntryList  ;
(gdb)
1188          if (w != NULL) {
(gdb) p w
$14 = (ObjectWaiter *) 0x0
(gdb) n
1207          w = _cxq ;
(gdb) p _cxq
$15 = (ObjectWaiter * volatile) 0x7fffdd57f600
(gdb) n
1208          if (w == NULL) continue ;
(gdb) p w
$16 = (ObjectWaiter *) 0x7fffdd57f600
(gdb) n
1214              assert (w != NULL, "Invariant") ;
(gdb)
1215              ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
(gdb) n
1216              if (u == w) break ;
(gdb)
1221          assert (w != NULL              , "invariant") ;
(gdb)
1222          assert (_EntryList  == NULL    , "invariant") ;
(gdb)
1233          if (QMode == 1) {
(gdb)
1252             _EntryList = w ;
(gdb)
1253             ObjectWaiter * q = NULL ;
(gdb) p _EntryList
$17 = (ObjectWaiter * volatile) 0x7fffdd57f600
(gdb) n
1255             for (p = w ; p != NULL ; p = p->_next) {
(gdb)
1256                 guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
(gdb)
1257                 p->TState = ObjectWaiter::TS_ENTER ;
(gdb)
1258                 p->_prev = q ;
(gdb)
1259                 q = p ;
(gdb)
1255             for (p = w ; p != NULL ; p = p->_next) {
(gdb)
1256                 guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
(gdb)
1257                 p->TState = ObjectWaiter::TS_ENTER ;
(gdb)
1258                 p->_prev = q ;
(gdb)
1259                 q = p ;
(gdb)
1255             for (p = w ; p != NULL ; p = p->_next) {
(gdb)
1256                 guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
(gdb)
1257                 p->TState = ObjectWaiter::TS_ENTER ;
(gdb)
1258                 p->_prev = q ;
(gdb)
1259                 q = p ;
(gdb)
1255             for (p = w ; p != NULL ; p = p->_next) {
(gdb)
1269          if (_succ != NULL) continue;
(gdb)
1271          w = _EntryList  ;
(gdb)
1272          if (w != NULL) {
(gdb)
1273              guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
(gdb)
1274              ExitEpilog (Self, w) ;
(gdb) p w->_thread->_osthread->_thread_id
$18 = 11719
(gdb) c
Continuing.
[Thread 0x7fffdd883700 (LWP 11716) exited]
Thread name: t4 id: 11
[Switching to Thread 0x7fffdd580700 (LWP 11719)]

Breakpoint 1, ObjectMonitor::exit (this=0x7fffc8002218, not_suspended=true, __the_thread__=0x7ffff01bf800)
    at /home/suntao/openjdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp:958
958    if (THREAD != _owner) {
(gdb) p _owner
$19 = (void * volatile) 0x7ffff01bf800
(gdb)

上述实验创建的线程如下:

线程名称 Thread类的getId方法返回值 内核线程ID JVM中的线程指针
t1 8 11716 0x7ffff01b9000
t2 9 11717 0x7ffff01bb000
t3 10 11718 0x7ffff01bd000
t4 11 11719 0x7ffff01bf800
  • 第一个断点暂停时参数__the_thread__是0x7ffff01b9000,这是线程t1的指针,此时t1正释放监视器锁,由于获得监视器锁时是从轻量级锁膨胀成监视器锁,因此_owner是轻量锁记录的指针,OwnerIsThread是0;
  • cxq队列是t4 -> t3 -> t2 -> NULL,_EntryList为空;
  • 第二个断点暂停时Knob_QMode是默认值0,cxq链表不为空但EntryList链表为空,因此cxq链表成为EntryList链表,不需要反转,然后新EntryList链表中的第一个线程被唤醒,即t4获得了监视器锁,这验证了Java中的synchronized关键字(三)一文论述的正确性。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,165评论 1 32
  • 又来到了一个老生常谈的问题,应用层软件开发的程序员要不要了解和深入学习操作系统呢? 今天就这个问题开始,来谈谈操...
    tangsl阅读 4,200评论 0 23
  • 经常可以在一些大牛的代码中看到const、static、extern的使用,特别是一些组合,例如extern + ...
    IUVO阅读 262评论 0 0
  • 2017年1月24日 办完登机牌,过完安检,又到写游记的时间了。匆匆的芬兰之行,留下了什么印记呢? 芬兰的树林 尽...
    龙岸山人阅读 450评论 0 0