调试监视器锁
2019-03-15 本文已影响0人
buzzerrookie
为了更深入地理解监视器锁,本文使用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关键字(三)一文论述的正确性。