网上各路大神总结过各类关于 hotspot jvm synchronized 内部实现,看别人的文章总以为不过瘾,因此有了这篇文章,尝试再扒一次 synchronized 的“底裤”java
在分析源代码以前须要了解相关概念,好比 oop, oopDesc, markOop 等,参考网络上各类解说或者以前系列文章,这里重点介绍一下 markOop,ObjectWaiter,ObjectMonitor .etc编程
每一个 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++ 系统编程中随处可见 app
对于 normal object,32 bit 位分为 4 个字段,其中和 synchronized 相关的是 biased_lock 和 lockjvm
hash,对象的 hash 值oop
age,对象的年龄,分代 GC 相关布局
biased_lock,偏向锁标志this
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
若是一个线程在等待 object monitor(对象监视器),虚拟机会建立一个 ObjectWaiter 对象,并经过 _next 和 _prev 指针将 ObjectWaiter 挂载到 object monitor 中的等待队列中
class ObjectWaiter : public StackObject { public: ObjectWaiter * volatile _next; ObjectWaiter * volatile _prev; Thread* _thread ... }
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 会膨胀(提高)成重量级实现
当前拥有该 ObjectMonitor 的线程
由 ObjectWaiter 组成的双向链表,JVM 会从该链表中取出一个 ObjectWaiter 并唤醒对应的 JavaThread
JVM 为每一个尝试进入 synchronized 代码段的 JavaThread 建立一个 ObjectWaiter 并添加到 _cxq 队列中
JVM 为每一个调用 Object.wait() 方法的线程建立一个 ObjectWaiter 并添加到 _WaitSet 队列中
在进入 synchronized 代码块或方法时,javac 会插入一条 monitorenter 字节码指令,退出时插入一条 monitorexit 指令,咱们仍是以 Zero 解释器为例来看看 monitorenter/monitorexit 指令是如何实现的,关于 Zero 解释器相关概念能够参考以前的文章
在 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 对象有一个 _owner 字段代表当前哪一个线程持有 ObjectMonitor,enter 方法首先经过 cmpxchg 尝试将 _owner 原子性设置成当前线程,若是成功就直接返回,这样能够避免进行内核线程的上下文切换