前言
网上各路大神总结过各种关于 hotspot jvm synchronized 内部实现,看别人的文章总觉得不过瘾,所以有了这篇文章,尝试再扒一次 synchronized 的“底裤”
数据结构
在分析源代码之前需要了解相关概念,比如 oop, oopDesc, markOop 等,参考网络上各种解说或者之前系列文章,这里重点介绍一下 markOop,ObjectWaiter,ObjectMonitor .etc
markOop
每个 Java Object 在 JVM 内部都有一个 native 的 C++ 对象 oop/oopDesc 与之对应,回顾一下 oopDesc 的类定义(内存布局)
class oopDesc {
private:
volatile markOop _mark;
}
_mark 被声明在 oopDesc 类的顶部,所以这个 _mark 可以认为是一个 头部(就像 TCP/IP 数据包头部),我们知道"头部"一般保存着一些重要的状态和标志信息,在 markOop.hpp 文件头部有一大段注释说明 markOop 内存布局
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
// size:32 ------------------------------------------>| (CMS free block)
// PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)
这里只列出 32 位机器上 markOop 的内存布局,同样的 32 bit 在不同的 object(normal, biased)以及不同的 CMS 垃圾搜集状态下有不同的解释,这种紧凑的内存复用技术在 C/C++ 系统编程中随处可见
对于 normal object,32 bit 位分为 4 个字段,其中和 synchronized 相关的是 biased_lock 和 lock
hash,对象的 hash 值
age,对象的年龄,分代 GC 相关
biased_lock,偏向锁标志
lock,对象锁标志
占两比特,用于描述 3 种状态 locked, unlocked, monitor
// [ptr | 00] locked ptr points to real header on stack
// [header | 0 | 01] unlocked regular object header
// [ptr | 10] monitor inflated lock (header is wapped out)
// [ptr | 11] marked used by markSweep to mark an object
// not valid at any other time
对于 biased boject,biased_lock 比特位被设置,如果对象被偏向锁定,拥有该偏向锁的线程指针被保存在 markOop 的高位
// [JavaThread* | epoch | age | 1 | 01] lock is biased toward given thread
// [0 | epoch | age | 1 | 01] lock is anonymously biased
ObjectWaiter
如果一个线程在等待 object monitor(对象监视器),虚拟机会创建一个 ObjectWaiter 对象,并通过 _next 和 _prev 指针将 ObjectWaiter 挂载到 object monitor 中的等待队列中
class ObjectWaiter : public StackObject {
public:
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread
...
}
ObjectMonitor
ObjectMonitor 类是对 对象监视器 的封装,由于比较重要(关键),objectMonitor.hpp 文件中对它进行了大段注释
// The ObjectMonitor class implements the heavyweight version of a
// JavaMonitor. The lightweight BasicLock/stack lock version has been
// inflated into an ObjectMonitor. This inflation is typically due to
// contention or use of Object.wait().
从注释可以看出 ObjectMonitor 是 JavaMonitor(对象锁)的一个重量级实现,而偏向锁和 stack lock(?)是另一种轻量级实现,当调用 Object.wait() 方法时,轻量级 JavaMonitor 会膨胀(提升)成重量级实现
关键字段
_owner
当前拥有该 ObjectMonitor 的线程
_EntryList
由 ObjectWaiter 组成的双向链表,JVM 会从该链表中取出一个 ObjectWaiter 并唤醒对应的 JavaThread
_cxq
JVM 为每个尝试进入 synchronized 代码段的 JavaThread 创建一个 ObjectWaiter 并添加到 _cxq 队列中
_WaitSet
JVM 为每个调用 Object.wait() 方法的线程创建一个 ObjectWaiter 并添加到 _WaitSet 队列中
synchronized 实现
在进入 synchronized 代码块或方法时,javac 会插入一条 monitorenter 字节码指令,退出时插入一条 monitorexit 指令,我们还是以 Zero 解释器为例来看看 monitorenter/monitorexit 指令是如何实现的,关于 Zero 解释器相关概念可以参考之前的文章
monitorenter
在 bytecodeInterpreter.cpp 中能够找到 monitorenter 对应的 case,大概流程如下:
获取方法隐含的 this 参数,即 oop
获取对象头部 markOop(参考上文),判断是否有偏向标志(has_bias_pattern),如果没有转到 4
偏向锁相关的处理逻辑
尝试使用轻量级锁,这里使用了 CAW(compare and swap,比较和交换)原语来保证线程对 oop 中 markOop
字段的独占写入,成功写入的线程立即返回(接着运行),失败的线程则调用 InterpreterRuntime::monitorenter
方法(重量级锁)
至此可以看出加锁的顺序:偏向锁 -> 轻量级锁 -> 重量级锁
CASE(_monitorenter): {
oop lockee = STACK_OBJECT(-1);
...
if (entry != NULL) {
entry->set_obj(lockee);
...
markOop mark = lockee->mark();
intptr_t hash = (intptr_t) markOopDesc::no_hash;
if (mark->has_bias_pattern()) {
// 尝试使用偏向锁...
}
// 尝试使用轻量级锁
// traditional lightweight locking
if (!success) {
markOop displaced = lockee->mark()->set_unlocked();
entry->lock()->set_displaced_header(displaced);
bool call_vm = UseHeavyMonitors;
if (call_vm || Atomic::cmpxchg_ptr(entry, lockee->mark_addr(), displaced) != displaced) {
// Is it simple recursive case?
if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) {
entry->lock()->set_displaced_header(NULL);
} else {
CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception);
}
}
}
} else {
istate->set_msg(more_monitors);
UPDATE_PC_AND_RETURN(0);
}
}
我们先把偏向锁相关的代码放一遍,接着看 InterpreterRuntime::monitorenter 方法,为了使代码更加清晰,我们忽略掉断言和条件编译,
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread,
BasicObjectLock* elem))
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
IRT_END
看来 JVM 还是不死心,这里又有两个分支 fast_enter 和 slow_enter,由于一路上我们都是挑着最慢的路径走,这回也不例外,接着扒 slow_enter 方法
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
if (mark->is_neutral()) {
lock->set_displaced_header(mark);
if (mark == (markOop) Atomic::cmpxchg_ptr(lock, obj()->mark_addr(), mark)) {
TEVENT(slow_enter: release stacklock);
return;
}
// Fall through to inflate() ...
} else if (mark->has_locker() &&
THREAD->is_lock_owned((address)mark->locker())) {
lock->set_displaced_header(NULL);
return;
}
lock->set_displaced_header(markOopDesc::unused_mark());
ObjectSynchronizer::inflate(THREAD,
obj(),
inflate_cause_monitor_enter)->enter(THREAD);
}
再次通过 cmpxchg 尝试轻量级锁,否则调用 ObjectSynchronizer:: inflate 方法膨胀成重量级锁(ObjectMonitor)并调用其 enter 方法
ObjectMonitor::enter
ObjectMonitor 对象有一个 _owner 字段表明当前哪个线程持有 ObjectMonitor,enter 方法首先通过 cmpxchg 尝试将 _owner 原子性设置成当前线程,如果成功就直接返回,这样可以避免进行内核线程的上下文切换
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。