部分参考与推荐阅读 《死磕Synchronized底层实现》
概念
- 确保线程互斥访问代码
- 确保共享变量修改可见
- 解决重排序问题
每个对象都拥有 monitor (监视器锁),当 monitor 被占用即为锁定状态,线程执行 monitorenter 指令时会尝试获取 monitor 的所有权。抽象流程:
- 当 monitor 进入数为 0 表示未被持有,该线程为 monitor 持有者并将进入数 +1
- 当线程已持有 monitor,重新进入,则进入数 +1
- monitor 被其他线程持有,进入数 >0,阻塞当前线程直到进入数为 0 再尝试获取
对比锁释放-获取的内存语义与volatile写-读的内存语义可以看出:锁释放与volatile写有相同的内存语义,锁获取与volatile读有相同的内存语义。 ——《Java 并发编程的艺术》
Demo
// Demo 1
public class SynchronizedTest {
public void method() {
synchronized (this) {
}
}
}
观察方法的字节码:
// access flags 0x1
public method()V
TRYCATCHBLOCK L0 L1 L2 null
TRYCATCHBLOCK L2 L3 L2 null
L4
LINENUMBER 3 L4
ALOAD 0
DUP
ASTORE 1
MONITORENTER
L0
LINENUMBER 4 L0
ALOAD 1
MONITOREXIT
L1
GOTO L5
L2
FRAME FULL [SynchronizedTest java/lang/Object] [java/lang/Throwable]
ASTORE 2
ALOAD 1
MONITOREXIT
L3
ALOAD 2
ATHROW
L5
LINENUMBER 5 L5
FRAME CHOP 1
RETURN
L6
LOCALVARIABLE this LSynchronizedTest; L4 L6 0
MAXSTACK = 2
MAXLOCALS = 3
}
public class SynchronizedTest {
public synchronized void method() {
}
}
观察字节码:
// access flags 0x21
public synchronized method()V
L0
LINENUMBER 3 L0
RETURN
L1
LOCALVARIABLE this LSynchronizedTest; L0 L1 0
MAXSTACK = 0
MAXLOCALS = 1
通过 jclasslib 可以明确方法的访问标志(也可以通过 flags 计算):

方法添加 synchronized 访问标志等同于在方法调用前获取 monitor,并在方法执行后释放。
结构
抽象
synchronized 作用于对象,因为 synchronized 的锁是存在对象头中的。
如果对象是数组类型,则虚拟机用 3 个字宽 (Word)存储对象头,如果对象是非数组类型,则用 2 字宽存储对象头。在32位虚拟机中,1 字宽等于 4 字节。
长 度 | 内 容 | 说 明 |
32/64 bit | Mark Word | 存储对象的 hashCode 或 锁信息 |
32/64 bit | Class Metadata Address | 存储到对象类型数据的指针 |
32/32 bit | Array Length | 数组的长度(如果当前对象是数组) |
32 位 Mark Word
锁状态 | 25 bit | 4 bit | 1 bit | 2 bit | |
---|---|---|---|---|---|
23 bit | 2 bit | 是否是偏向锁 | 锁标志位 | ||
无锁状态 | 对象的 hashCode | 对象分代年龄 | 0 | 01 | |
轻量级锁 | 指向栈中锁记录的指针 | 00 | |||
重量级锁 | 指向互斥量(重量级锁)的指针 | 10 | |||
GC 标记 | 空 | 11 | |||
偏向锁 | 线程 ID | Epoch | 对象分代年龄 | 1 | 01 |
64 位 Mark Word
锁状态 | 25 bit | 31 bit | 1 bit | 4 bit | 1 bit | 2 bit |
---|---|---|---|---|---|---|
cms_free | 分代年龄 | 偏向锁 | 锁标志位 | |||
无锁 | unused | hashCode | 0 | 01 | ||
偏向锁 | ThreadID(54 bit) Epoch(2 bit) | 1 | 01 |

Lock Record 则是存在栈中,用于轻量级锁优化。当解释器执行 monitorenter 字节码轻度锁住一个对象时,就会在获取锁的线程的栈上显式或者隐式分配一个 Lock Record。
底层
以上是较为官方的说法,那么 Lock Record 到底以如何形式存放在哪个位置,网上很多博客的解释都比较模糊。首先要说明,不同虚拟机的栈内部组织结构实现是不同的,这里仅以 HotSpot VM 来演示。HSDB相关内容请看R大的博客
在 jdk8u_hotspot 源码中,我们能找到 BasicObjectLock 这个类,大致是这样:
// 类 BasicObjectLock,包含对象头和对象的整体结构,也叫 Lock Record
class BasicObjectLock {
private:
// 对象头 (displaced_hdr)
BasicLock _lock;
// 持有该锁的对象指针 (owner)
oop _obj;
}
其内部包含了一个 BasicLock:
类 BasicLock,实现对象头的结构
class BasicLock {
private:
volatile markOop _displaced_header;
}
那么这玩意的位置在哪里呢?
这里我们需要借助 HSDB(HotSpot Debugger),我写了一个快速启动 bat 放在下面:
cd /
cd %JAVA_HOME%/bin
jps
cd /
cd %JAVA_HOME%\lib
java -cp ./sa-jdi.jar sun.jvm.hotspot.HSDB
ps:如果 jps 无法正常使用,请将 %JAVA_HOME%/jre/bin/sawindbg.dll 复制到 %JAVA_HOME%/bin/ 文件夹内。
这里我写了一个 demo,如下:
public class SynchronizedTest {
static void doNothing() {
}
public static void main(String[] args) {
doNothing();
}
}
我们在 doNothing() 方法前打上断点,然后通过 HSDB 去获取 main 方法的 内存栈结构,我们能看到如下内容:

之后,我们在 doNothing() 方法前添加 synchronized,再次查看内存中栈帧的内容:

可以看到,BasicObjectLocks,也就是 Lock Record 存放在靠近当前栈帧顶部的位置,比操作数栈略靠近栈底一点点(操作数栈一般都是在栈顶的)。我自己是通过这张图来理解后面的源码部分,为什么要选择两个指针作为范围来获取 Lock Record。

如果有对栈的基础知识不了解,请参阅 Chapter 5 of Inside the Java Virtual Machine
The Java Virtual Machine
前置知识
这里默认读者跟我一样对 C 语言不太精通,因此我会把一些过程中陌生的知识点放在这里,如果已经了解请跳过。
intptr_t
An integer type that can be cast to/from pointer without loss of data.
用来存放地址,intptr_t 在不同平台上不一样,始终与地址位数相同,因此保证通用性。
#if 0 -- #endif
类似于 java 中的 /* */
使用预处理器屏蔽一段代码
源代码
首先要交代以下情况:
- hotspot 使用了两种解释器,模板解释器 (templateInterpreter) 和 C++解释器 (bytecodeInterpreter)
- hotspot 默认使用的是 templateInterpreter,也可以使用 C++解释器,所以两者实现大同小异
- templateInterpreter 使用汇编,因此网上大部分文章通过 bytecodeInterpreter 来研究其逻辑
bytecodeInterpreter => case(_monitorenter) 部分
// monitorenter 进入的位置 CASE(_monitorenter): // 获取锁对象 oop lockee = STACK_OBJECT(-1); // 触发 null 检查 CHECK_NULL(lockee); /** * 找到一个空闲的 monitor 或者一个已经分配给当前对象的 monitor * 如果我们找到一个匹配的对象,我们就需要一个新的 monitor * 因为这是递归进入(重入) * * 类 BasicObjectLock,包含对象头和对象的整体结构,也叫 Lock Record * class BasicObjectLock { * private: * // 对象头 * BasicLock _lock; * // 持有该锁的对象指针 (owner) * oop _obj; * } * * 类 BasicLock,实现对象头 * class BasicLock { * private: * volatile markOop _displaced_header; * } * * 指向栈帧中 Lock Record 起始位置 * inline BasicObjectLock* monitor_base() { * // base of monitors on the native stack * return _monitor_base; * } * * 指向栈帧中操作数栈的起始位置的指针 * inline intptr_t* stack_base() { * // base of expression stack * return _stack_base; * } * * HotSpot 中的虚拟机栈和本地方法栈共享一个栈 * 当前帧的操作数栈始终位于栈的顶部 */ // 获取栈帧中 Local Record 的起始位置 BasicObjectLock* limit = istate->monitor_base(); // 获取操作数栈中的起始位置指针 BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base(); BasicObjectLock* entry = NULL; // 指针由内存地址低处慢慢上升(从靠近栈顶处慢慢下降,因为操作数栈比 Lock Record 更接近栈顶) // 用于拿到一个空闲的锁记录 while (most_recent != limit) { // 如果 most_recent 锁记录的 owner 为空,则先拿到空闲的锁记录 if (most_recent->obj() == NULL) entry = most_recent; // 如果 most_recent 锁记录的 owner 为当前锁对象,跳出 else if (most_recent->obj() == lockee) break; // 将 most_recent 增加,继续搜寻下一条锁记录,直到达到 limit most_recent++; } // 如果 entry 标记的锁记录不为空 if (entry != NULL) { // 将锁记录的 owner 设定为当前锁对象 entry->set_obj(lockee); int success = false; // epoch 掩码 uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place; // 获取锁对象的对象头(主要存储了 Mark Word) markOop mark = lockee->mark(); intptr_t hash = (intptr_t) markOopDesc::no_hash; // implies UseBiasedLocking // 是否禁用偏向锁 if (mark->has_bias_pattern()) { // 线程id uintptr_t thread_ident; // 预期偏向锁值 uintptr_t anticipated_bias_locking_value; // 获取当前线程 id thread_ident = (uintptr_t)istate->thread(); anticipated_bias_locking_value = // 1. 对当前锁对象 class 的 prototype_header (原始的头部信息) // 和 线程id 进行 或运算,相当于将 线程信息 放到 原始头部中 // prototype_header (epoch + 分代年龄 + 锁偏向标志 + 锁标志位) (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) // 2. 拿锁对象的 对象头 进行 异或运算 // 如果当前线程持有锁,线程 ID 的几位则为 0 ^ (uintptr_t)mark) // 3. 获取 age_mask_in_place (二进制 1111 000),取反 0000 111 // 与之前的运算结果进行 与运算 (遮盖分代年龄 0000,保留锁的 111) // 这一步用于去除年龄位,保留锁位和其他,去掉 GC 的影响 & ~((uintptr_t) markOopDesc::age_mask_in_place); // 偏向的线程是否为当前线程 // anticipated_bias_locking_value == 0 说明 // prototype_header | thread_id 的后三位与 mark 后三位以及 epoch 相同 // 表明偏向的线程为当前线程 if (anticipated_bias_locking_value == 0) { // already biased towards this thread, nothing to do // 打印偏向锁定统计信息 if (PrintBiasedLockingStatistics) { (* BiasedLocking::biased_lock_entry_count_addr())++; } success = true; } // 预期偏向锁值 与 biased_lock_mask_in_place (二进制 111) 做 与运算 // 结果不为 0 // 这里 mark 后三位为 101,要求 prototype_header 与 101 异或 不为 0 // 等价于要求 prototype_header 不为 101,(偏向锁和标志位正是 101) // 等价于要求 prototype_header 不是偏向锁 else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) { // try revoke bias // 因为没有开启偏向锁,因此获取 锁对象 的原型(无偏向锁)对象头 markOop header = lockee->klass()->prototype_header(); // 如果 hash 不为 0(这里 no_hash = 0),则对 对象头做处理 并返回 if (hash != markOopDesc::no_hash) { /** * copy_set_hash 大致操作为 * 获取当前对象头的无符号地址 tmp,将 hash_mask_in_place 取反后,进行与运算 * 也就是 tmp 中 hash 部分被置 0 * 通过或操作,将 hash 中对应部分赋到 tmp 中(正好补上刚刚被置 0 的位置) * 返回 tmp * 由此获得一个新 header */ header = header->copy_set_hash(hash); } /** * CAS 操作 * Atomic::cmpxchg_ptr(intptr_t exchange, volatile intptr_t* dest, intptr_t compare) * 将 dest* 指向的内容和 compare 比较 * 如果相同,则将 exchange 写入 dest 指向的内容,返回没变动的 compare * 如果不同,则将 dest 指向的内容写入 compare,并返回 compare * * 所以这里 if() 中如果为 true 则表示更变成功 * false 表示更变失败 * * 注意这里的关系: * oop lockee (锁对象) * header = lockee->klass()->prototype_header() 返回 _prototype_header: * 在此类型启用和禁用偏向锁定时使用 * * markOop mark = lockee->mark() 锁对象中的 mark word * lockee->mark_addr() = (markOop*) &_mark 锁对象中的 mark word 的指针 * * 当前作用: * 将 lockee->mark_addr() 指向的内容和当前 mark 比较 * 如果相同,表示锁未被其他线程获取 * header 写入 lockee->mark_addr(),返回 mark,true * 如果不同,将 lockee->mark_addr() 指向的对象写入 mark,返回 mark,false * * 当然,通常来说 锁对象的 mark word 指针都是指向当前 mark word * 如果其他线程没有进入并进行修改,就用处理过的 prototype_header 替换 * 类似于初始化 锁对象 的对象头 */ if (Atomic::cmpxchg_ptr(header, lockee->mark_addr(), mark) == mark) { if (PrintBiasedLockingStatistics) (*BiasedLocking::revoked_lock_entry_count_addr())++; } } // 预期偏向锁值 和 epoch (768, epoch 位为 11) 掩码 与操作,不为 0 则表示过期 // 进入此区块表示偏向锁启用,但没有偏向当前线程 else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) { // try rebias // 尝试重偏向 // 构造 lockee 偏向当前线程的 新原型对象头(原型对象头 与 线程id 按位或操作) markOop new_header = (markOop) ((intptr_t) lockee->klass()->prototype_header() | thread_ident); // 与上面 header 操作同理 if (hash != markOopDesc::no_hash) { new_header = new_header->copy_set_hash(hash); } /** * 重偏向到当前线程,并会更新持有锁的时间(epoch) * 进行 lockee 的头部替换 */ if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), mark) == mark) { if (PrintBiasedLockingStatistics) (* BiasedLocking::rebiased_lock_entry_count_addr())++; } else { // 失败表示多个线程同时竞争,升级到轻量级锁 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); } success = true; } else { // try to bias towards thread in case object is anonymously biased // 进入这里的逻辑代表匿名偏向锁 // 他是偏向锁,但没有被线程所持有 // 因此这里构建匿名偏向的 mark word // 后面的掩码值由三部分异或组成, 值为 895,二进制 11 0111 1111 // 保留了除了 线程id 外所有信息 markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place |(uintptr_t)markOopDesc::age_mask_in_place |epoch_mask_in_place)); if (hash != markOopDesc::no_hash) { header = header->copy_set_hash(hash); } // 将当前 线程id 赋予头部 markOop new_header = (markOop) ((uintptr_t) header | thread_ident); // debugging hint DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);) // 尝试替换 lockee 的头部 if (Atomic::cmpxchg_ptr((void*)new_header, lockee->mark_addr(), header) == header) { if (PrintBiasedLockingStatistics) (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++; } else { // 锁升级 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); } success = true; } } // traditional lightweight locking // 不成功则进入 轻量级锁 if (!success) { // 构造无锁状态的 displaced mark word markOop displaced = lockee->mark()->set_unlocked(); /** * 空闲锁记录 (BasicObjectLock) 的 lock() 方法 会返回 * new BasicLock(addr.addOffsetTo(lockField.getOffset())) * 内部存有 _displaced_header,可以理解为 对象头或 mark word * * 这里拿到空闲锁记录 BasicLock,将 displaced mark word 赋进去 */ entry->lock()->set_displaced_header(displaced); // 是否使用重量级锁 bool call_vm = UseHeavyMonitors; // 熟悉的老套路,使用 cmpxchg_ptr 进行 CAS 替换 // 先判断是否使用重量级锁,如果 call_vm 为 true 会直接进入 CALL_VM 部分 // 如果 CAS 替换不成功,代表对象不是无锁状态 // 则进入判断是否锁重入,轻量级锁的锁重入使用 Lock Record 的数量记录 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())) { // 如果锁重入,将 displaced mark word 设为 null entry->lock()->set_displaced_header(NULL); } else { // 锁升级 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); } } } UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); } else { istate->set_msg(more_monitors); UPDATE_PC_AND_RETURN(0); // Re-execute } }

在 Lightweight Locking,也就是轻量级锁的逻辑中,会涉及到锁重入。
如果 CAS 替换失败,代表当前对象为有锁状态。即涉及锁争夺的情况,不过在膨胀成重量级锁之前,要排除锁重入的情况(外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码)。
HotSpot 选择记录重入的方式是,在原先的 Lock Record 下新增一个 Displaced Mark Word 为 NULL 的 Lock Record,对应解锁时,进入多少次(有多少个 Lock Record)就退出多少次。

InterpreterRuntime::monitorenter
在上面的代码逻辑中,我们可以看到锁升级调用了
InterpreterRuntime::monitorenter(THREAD, entry) 来升级锁。
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem)) #ifdef ASSERT thread->last_frame().interpreter_frame_verify_monitor(elem); #endif if (PrintBiasedLockingStatistics) { // 对 &_slow_path_entry_count 自增 Atomic::inc(BiasedLocking::slow_path_entry_count_addr()); } Handle h_obj(thread, elem->obj()); assert(Universe::heap()->is_in_reserved_or_null(h_obj()), "must be NULL or an object"); if (UseBiasedLocking) { // Retry fast entry if bias is revoked to avoid unnecessary inflation // 为了避免不必要的膨胀,在偏向锁撤销后尝试快速进入 // 1,开启偏向锁进入 ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK); } else { // 慢进入逻辑 // 2,未开启偏向锁进入 ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK); } assert(Universe::heap()->is_in_reserved_or_null(elem->obj()), "must be NULL or an object"); #ifdef ASSERT thread->last_frame().interpreter_frame_verify_monitor(elem); #endif IRT_END
这个关键节点,将之后的逻辑又拆分为两个分支,fast_enter 和 slow_enter。

fast_enter & revoke_and_rebias & revoke_bias
fast_enter
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias, TRAPS) { if (UseBiasedLocking) { // 启用偏向锁,当不在全局安全点时 if (!SafepointSynchronize::is_at_safepoint()) { BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD); if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) { return; } } // 在全局安全点时 else { assert(!attempt_rebias, "can not rebias toward VM thread"); BiasedLocking::revoke_at_safepoint(obj); } assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now"); } // fast_enter 未处理成功,转入 slow_enter 继续处理 slow_enter (obj, lock, THREAD) ; }
revoke_and_rebias
BiasedLocking::Condition BiasedLocking::revoke_and_rebias(Handle obj, bool attempt_rebias, TRAPS) { assert(!SafepointSynchronize::is_at_safepoint(), "must not be called while at safepoint"); // We can revoke the biases of anonymously-biased objects // efficiently enough that we should not cause these revocations to // update the heuristics because doing so may cause unwanted bulk // revocations (which are expensive) to occur. // 因为我们撤销匿名偏向对象的偏向效率足够高,因此我们尽量避免当前废除操作去更新 heuristics // 因为这样会导致开销增大 // 获取对象 mark word markOop mark = obj->mark(); // 处理匿名偏向 且 attempt_rebias 为 false,需要进行偏向锁撤销 if (mark->is_biased_anonymously() && !attempt_rebias) { // We are probably trying to revoke the bias of this object due to // an identity hash code computation. Try to revoke the bias // without a safepoint. This is possible if we can successfully // compare-and-exchange an unbiased header into the mark word of // the object, meaning that no other thread has raced to acquire // the bias of the object. // 由于身份哈希码计算,我们可能试图撤销该对象的偏向 // 尝试在没有安全点的情况下撤销偏向 // 成功使用 CAS 将 无偏原型对象头 赋入锁对象的 mark word 时, // 表明没有其他线程竞争获取对象的偏向 // mark word markOop biased_value = mark; // 新构造的 无偏原型对象头 markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); // CAS 成功返回 mark,失败返回 obj->mark_addr() markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); // CAS 赋值成功,成功撤销偏向锁 if (res_mark == biased_value) { return BIAS_REVOKED; } } // 开启偏向锁 else if (mark->has_bias_pattern()) { Klass* k = obj->klass(); markOop prototype_header = k->prototype_header(); if (!prototype_header->has_bias_pattern()) { // This object has a stale bias from before the bulk revocation // for this data type occurred. It's pointless to update the // heuristics at this point so simply update the header with a // CAS. If we fail this race, the object's bias has been revoked // by another thread so we simply return and let the caller deal // with it. // 对象在发生此数据类型的批量撤销之前具有未更新的偏向 // (obj 的 mark word 开启偏向但其 class 类并未开启)。 // 此时更新 heuristics 是没有意义的,只需使用 CAS 更新对象头即可。 // 如果我们在这次锁竞争中失败了,说明对象的偏向已经被另一个线程撤销了, // 我们只需返回并让调用者处理它。 markOop biased_value = mark; markOop res_mark = (markOop) Atomic::cmpxchg_ptr(prototype_header, obj->mark_addr(), mark); assert(!(*(obj->mark_addr()))->has_bias_pattern(), "even if we raced, should still be revoked"); return BIAS_REVOKED; } else if (prototype_header->bias_epoch() != mark->bias_epoch()) { // The epoch of this biasing has expired indicating that the // object is effectively unbiased. Depending on whether we need // to rebias or revoke the bias of this object we can do it // efficiently enough with a CAS that we shouldn't update the // heuristics. This is normally done in the assembly code but we // can reach this point due to various points in the runtime // needing to revoke biases. // 当前偏向已经过期,该对象实际上应是无偏向的。 // 我们可以使用 CAS 去重新偏向或撤销该对象的偏向, // 我们不应该更新 heuristics, // 通常在汇编代码中完成,但由于运行时中的各个点需要撤销偏向,因此设计达到这一点。 if (attempt_rebias) { assert(THREAD->is_Java_thread(), ""); markOop biased_value = mark; markOop rebiased_prototype = markOopDesc::encode((JavaThread*) THREAD, mark->age(), prototype_header->bias_epoch()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(rebiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED_AND_REBIASED; } } else { markOop biased_value = mark; markOop unbiased_prototype = markOopDesc::prototype()->set_age(mark->age()); markOop res_mark = (markOop) Atomic::cmpxchg_ptr(unbiased_prototype, obj->mark_addr(), mark); if (res_mark == biased_value) { return BIAS_REVOKED; } } } } // 批量重偏向,批量撤销 HeuristicsResult heuristics = update_heuristics(obj(), attempt_rebias); if (heuristics == HR_NOT_BIASED) { return NOT_BIASED; } // 撤销单个线程 else if (heuristics == HR_SINGLE_REVOKE) { Klass *k = obj->klass(); markOop prototype_header = k->prototype_header(); // 需要撤销的是偏向当前线程的锁 if (mark->biased_locker() == THREAD && prototype_header->bias_epoch() == mark->bias_epoch()) { // A thread is trying to revoke the bias of an object biased // toward it, again likely due to an identity hash code // computation. We can again avoid a safepoint in this case // since we are only going to walk our own stack. There are no // races with revocations occurring in other threads because we // reach no safepoints in the revocation path. // Also check the epoch because even if threads match, another thread // can come in with a CAS to steal the bias of an object that has a // stale epoch. // 撤销偏向当前线程的锁,这可能是由于身份哈希码计算。 // 在这种情况下,我们可以避免进入安全点, // 因为我们只遍历当前线程自己的堆栈,没有在其他线程中发生撤销的竞争。 // 还要检查 epoch,因为即使线程匹配, // 另一个线程也可以使用 CAS 替换具有过时 epoch 的对象的偏向。 ResourceMark rm; if (TraceBiasedLocking) { tty->print_cr("Revoking bias by walking my own stack:"); } BiasedLocking::Condition cond = revoke_bias(obj(), false, false, (JavaThread*) THREAD); ((JavaThread*) THREAD)->set_cached_monitor_info(NULL); assert(cond == BIAS_REVOKED, "why not?"); return cond; } else { // 在 VM 线程中的安全点调用 revoke_bias VM_RevokeBias revoke(&obj, (JavaThread*) THREAD); VMThread::execute(&revoke); return revoke.status_code(); } } assert((heuristics == HR_BULK_REVOKE) || (heuristics == HR_BULK_REBIAS), "?"); // VM 线程调用 批量撤销偏向,批量重偏向 VM_BulkRevokeBias bulk_revoke(&obj, (JavaThread*) THREAD, (heuristics == HR_BULK_REBIAS), attempt_rebias); VMThread::execute(&bulk_revoke); return bulk_revoke.status_code(); }

revoke_bias
static BiasedLocking::Condition revoke_bias(oop obj, bool allow_rebias, bool is_bulk, JavaThread* requesting_thread) { markOop mark = obj->mark(); // 禁用偏向锁,返回不偏向 if (!mark->has_bias_pattern()) { if (TraceBiasedLocking) { ResourceMark rm; tty->print_cr(" (Skipping revocation of object of type %s because it's no longer biased)", obj->klass()->external_name()); } return BiasedLocking::NOT_BIASED; } // 构造有偏和无偏原型对象头并设置 age 为当前对象头的部分 uint age = mark->age(); markOop biased_prototype = markOopDesc::biased_locking_prototype()->set_age(age); markOop unbiased_prototype = markOopDesc::prototype()->set_age(age); if (TraceBiasedLocking && (Verbose || !is_bulk)) { ResourceMark rm; tty->print_cr("Revoking bias of object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s , prototype header " INTPTR_FORMAT " , allow rebias %d , requesting thread " INTPTR_FORMAT, p2i((void *)obj), (intptr_t) mark, obj->klass()->external_name(), (intptr_t) obj->klass()->prototype_header(), (allow_rebias ? 1 : 0), (intptr_t) requesting_thread); } // 获取偏向线程 JavaThread* biased_thread = mark->biased_locker(); if (biased_thread == NULL) { // Object is anonymously biased. We can get here if, for // example, we revoke the bias due to an identity hash code // being computed for an object. // 对象为匿名偏向的情况,我们可以通过身份哈希码计算出对象来撤销偏向 // 如果不允许重偏向,将当前对象的 mark word 设置为无偏 if (!allow_rebias) { obj->set_mark(unbiased_prototype); } if (TraceBiasedLocking && (Verbose || !is_bulk)) { tty->print_cr(" Revoked bias of anonymously-biased object"); } return BiasedLocking::BIAS_REVOKED; } // Handle case where the thread toward which the object was biased has exited // 处理线程偏向对象退出的情况 bool thread_is_alive = false; // 如果当前线程就是偏向线程,即偏向线程还存活 if (requesting_thread == biased_thread) { thread_is_alive = true; } else { // 遍历 jvm 线程,找到说明偏向线程还活着 for (JavaThread* cur_thread = Threads::first(); cur_thread != NULL; cur_thread = cur_thread->next()) { if (cur_thread == biased_thread) { thread_is_alive = true; break; } } } // 如果偏向线程已经不存活 if (!thread_is_alive) { // 如果允许重偏向 if (allow_rebias) { // 给对象设置新的有偏对象头 obj->set_mark(biased_prototype); } else { // 设置无偏对象头 obj->set_mark(unbiased_prototype); } if (TraceBiasedLocking && (Verbose || !is_bulk)) { tty->print_cr(" Revoked bias of object biased toward dead thread"); } return BiasedLocking::BIAS_REVOKED; } // Thread owning bias is alive. // Check to see whether it currently owns the lock and, if so, // write down the needed displaced headers to the thread's stack. // Otherwise, restore the object's header either to the unlocked // or unbiased state. // 线程拥有偏向仍然存在。 // 检查它当前是否拥有锁,如果是,则将所需的移位头写到线程的堆栈中。 // 否则,将对象头恢复为未锁定或无偏状态。 // 线程还存活,则遍历所有线程栈中所有 Lock Record // 注意这里是由低到高遍历 Lock Record,这样最后 highest_lock 记录的才为最高 GrowableArray<MonitorInfo*>* cached_monitor_info = get_or_compute_monitor_info(biased_thread); BasicLock* highest_lock = NULL; for (int i = 0; i < cached_monitor_info->length(); i++) { MonitorInfo* mon_info = cached_monitor_info->at(i); // 如果 monitor 的持有者为当前对象,能找到 Lock Record 说明记录的线程还在执行 if (mon_info->owner() == obj) { if (TraceBiasedLocking && Verbose) { tty->print_cr(" mon_info->owner (" PTR_FORMAT ") == obj (" PTR_FORMAT ")", p2i((void *) mon_info->owner()), p2i((void *) obj)); } // Assume recursive case and fix up highest lock later // 升级为轻量级锁,直接修改线程中的 Lock Record // 构建时 displaced mark word 为 null,之所以这样是假设存在锁重入情况 // 后续代码会补全信息 markOop mark = markOopDesc::encode((BasicLock*) NULL); highest_lock = mon_info->lock(); highest_lock->set_displaced_header(mark); } else { if (TraceBiasedLocking && Verbose) { tty->print_cr(" mon_info->owner (" PTR_FORMAT ") != obj (" PTR_FORMAT ")", p2i((void *) mon_info->owner()), p2i((void *) obj)); } } } // 记录了最高位的 Lock Record 不为 null if (highest_lock != NULL) { // Fix up highest lock to contain displaced header and point // object at it // 修改第一个Lock Record 的 displaced mark word 为无锁状态 highest_lock->set_displaced_header(unbiased_prototype); // Reset object header to point to displaced mark. // Must release storing the lock address for platforms without TSO // ordering (e.g. ppc). // 将对象的 mark word 指向该 Lock Record // 必须为没有 TSO 排序的平台(例如 ppc)释放存储锁地址。 obj->release_set_mark(markOopDesc::encode(highest_lock)); assert(!obj->mark()->has_bias_pattern(), "illegal mark state: stack lock used bias bit"); if (TraceBiasedLocking && (Verbose || !is_bulk)) { tty->print_cr(" Revoked bias of currently-locked object"); } } else { if (TraceBiasedLocking && (Verbose || !is_bulk)) { tty->print_cr(" Revoked bias of currently-unlocked object"); } // 线程不在同步块中,允许重偏向 if (allow_rebias) { // 设置为匿名偏向状态 obj->set_mark(biased_prototype); } else { // Store the unlocked value into the object's header. // 设置为无锁状态 obj->set_mark(unbiased_prototype); } } return BiasedLocking::BIAS_REVOKED; }

slow_enter & inflate
下面来看锁争夺时膨胀为重量级锁的部分。
// Interpreter/Compiler Slow Case // This routine is used to handle interpreter/compiler slow case // We don't need to use fast path here, because it must have been // failed in the interpreter/compiler code. // 进入当前逻辑说明 快速加锁流程失败了 // 多个线程同时竞争锁时进入 void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) { // 拿到对象 mark word markOop mark = obj->mark(); // 声明禁用了偏向锁 assert(!mark->has_bias_pattern(), "should not see bias pattern here"); // 如果当前是无锁状态 if (mark->is_neutral()) { // Anticipate successful CAS -- the ST of the displaced mark must // be visible <= the ST performed by the CAS. // 将当前的 mark word 设置到 displaced mark word lock->set_displaced_header(mark); // CAS 替换加锁,如果成功就 return 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())) { assert(lock != mark->locker(), "must not re-lock the same lock"); assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock"); // 锁重入时需要设定 displaced mark word 为 null lock->set_displaced_header(NULL); return; } #if 0 // The following optimization isn't particularly useful. if (mark->has_monitor() && mark->monitor()->is_entered(THREAD)) { lock->set_displaced_header (NULL) ; return ; } #endif // The object header will never be displaced to this lock, // so it does not matter what the value is, except that it // must be non-zero to avoid looking like a re-entrant lock, // and must not look locked either. // 对象头永远不会被置换到这个锁,所以它的值是什么并不重要, // 但它必须非零以避免被误认为可重入锁,并且也不能看起来像已被锁。 lock->set_displaced_header(markOopDesc::unused_mark()); ObjectSynchronizer::inflate(THREAD, obj())->enter(THREAD); }

inflate
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) { // Inflate mutates the heap ... // Relaxing assertion for bug 6320749. assert (Universe::verify_in_progress() || !SafepointSynchronize::is_at_safepoint(), "invariant") ; for (;;) { const markOop mark = object->mark() ; assert (!mark->has_bias_pattern(), "invariant") ; // The mark can be in one of the following states: // * Inflated 重量级锁 - just return 返回 // * Stack-locked 轻量级锁 - coerce it to inflated 膨胀 // * INFLATING 膨胀中 - busy wait for conversion to complete 等待 // * Neutral 无锁 - aggressively inflate the object. 膨胀 // * BIASED 偏向锁 - Illegal. We should never see this 非法情况 // CASE: inflated 重量级锁 if (mark->has_monitor()) { // 基本就是直接获取并返回 ObjectMonitor * inf = mark->monitor() ; assert (inf->header()->is_neutral(), "invariant"); assert (inf->object() == object, "invariant") ; assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid"); return inf ; } // CASE: inflation in progress - inflating over a stack-lock. // 在轻量级锁中膨胀 // 其他线程正在膨胀此对象的轻量级锁 // 只有正在执行膨胀的线程可以完成膨胀操作,其他线程必须等待 // INFLATING 标志会转变 // 我们通过 spin/yield/park 和轮询等一系列操作,等待膨胀结束 // We could always eliminate polling by parking the thread on some auxiliary list. // 我们总是可以通过将线程停在某个辅助列表上来消除轮询 if (mark == markOopDesc::INFLATING()) { // 其他线程在操作此对象执行锁膨胀,自旋重试 TEVENT (Inflate: spin while INFLATING) ; ReadStableMark(object) ; continue ; } // CASE: stack-locked // 可以同时被当前线程和其他线程加轻量级锁 // Note that we allocate the objectmonitor speculatively, _before_ attempting // to install INFLATING into the mark word. We originally installed INFLATING, // allocated the objectmonitor, and then finally STed the address of the // objectmonitor into the mark. This was correct, but artificially lengthened // the interval in which INFLATED appeared in the mark, thus increasing // the odds of inflation contention. // // 注意,我们推测性地分配了对象监视器,_before_ 试图给 mark word 标记 INFLATING // 我们最初标记的 INFLATING,分配了对象监视器,最后将对象监视器的地址 STed 到了 mark 中 // 这个过程人为地延长了 INFLATED 出现在标记中的间隔,从而增加了膨胀争用的几率 // // We now use per-thread private objectmonitor free lists. // These list are reprovisioned from the global free list outside the // critical INFLATING...ST interval. A thread can transfer // multiple objectmonitors en-mass from the global free list to its local free list. // This reduces coherency traffic and lock contention on the global free list. // Using such local free lists, it doesn't matter if the omAlloc() call appears // before or after the CAS(INFLATING) operation. // See the comments in omAlloc(). // // 现在我们使用线程私有的对象监视器空闲列表(本地空闲列表) // 这个表格是从 INFLATING...ST 间隔之外的全局空闲列表中重新配置的 // 一个线程可以将多个对象监视器从全局空闲列表整体传输到其本地空闲列表 // 这减少了全局空闲列表上的一致性流量和锁定争用 // 使用这样的本地空闲列表,omAlloc() 调用出现在 CAS(INFLATING) 操作之前还是之后都没有关系 if (mark->has_locker()) { // 分配 ObjectMonitor // omAlloc 方法中会先从线程私有的 monitor 集合 omFreeList 中分配对象, // 如果 omFreeList 中没有 monitor 对象, // 则从 JVM 全局的 gFreeList 中分配一批 monitor 到 omFreeList 中 ObjectMonitor * m = omAlloc (Self) ; // Optimistically prepare the objectmonitor - anticipate successful CAS // We do this before the CAS in order to minimize the length of time // in which INFLATING appears in the mark. // 乐观地准备对象监视器 // 我们在 CAS 之前完成这些工作,目的是降低 INFLATING 出现在标记中的时长 // 对 ObjectMonitor 进行初始化 // Recycle() 定义在 objectMonitor.hpp 中,用途为初始化 m->Recycle(); // objectMonitor.hpp 并未给出解释 // 根据 docs 得知是责任线程 m->_Responsible = NULL ; // owner 是 线程 还是 SP/BasicLock(owner 可以是指向当前 Monitor 的线程 or BasicLock 的指针) m->OwnerIsThread = 0 ; // 重入计数 m->_recursions = 0 ; // 自旋周期时间 m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class // 使用 CAS 将锁对象的 mark word 修改为 INFLATING markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ; if (cmp != mark) { // 释放 om,继续循环重试 omRelease (Self, m, true) ; continue ; // Interference -- just retry } // We've successfully installed INFLATING (0) into the mark-word. // This is the only case where 0 will appear in a mark-work. // Only the singular thread that successfully swings the mark-word // to 0 can perform (or more precisely, complete) inflation. // // 我们已经成功将 INFLATING(0) 装载进 mark word // 这是 0 出现在 mark word 的唯一情况 // 只有成功地将 mark word 变为 0 的单个线程才能执行(准确地说,完全)膨胀。 // // Why do we CAS a 0 into the mark-word instead of just CASing the // mark-word from the stack-locked value directly to the new inflated state? // Consider what happens when a thread unlocks a stack-locked object. // It attempts to use CAS to swing the displaced header value from the // on-stack basiclock back into the object header. Recall also that the // header value (hashcode, etc) can reside in (a) the object header, or // (b) a displaced header associated with the stack-lock, or (c) a displaced // header in an objectMonitor. The inflate() routine must copy the header // value from the basiclock on the owner's stack to the objectMonitor, all // the while preserving the hashCode stability invariants. If the owner // decides to release the lock while the value is 0, the unlock will fail // and control will eventually pass from slow_exit() to inflate. The owner // will then spin, waiting for the 0 value to disappear. Put another way, // the 0 causes the owner to stall if the owner happens to try to // drop the lock (restoring the header from the basiclock to the object) // while inflation is in-progress. This protocol avoids races that might // would otherwise permit hashCode values to change or "flicker" for an object. // Critically, while object->mark is 0 mark->displaced_mark_helper() is stable. // 0 serves as a "BUSY" inflate-in-progress indicator. // 为什么我们要 CAS 一个 0 到 mark word 中, // 而不是直接将 mark word 从堆栈锁定值 CAS 到新的膨胀状态? // 我们必须考虑当线程解锁堆栈锁定对象时会发生什么。 // 它尝试使用 CAS 将 displaced header 从堆栈上的 basiclock 转换回对象头。 // header (hash code 等)可以驻留在: // (a) 对象头中 // (b) 与堆栈锁关联的 displaced header 中 // (c) objectMonitor 中的 displaced header 中 // inflate() 事务必须将 header 从所有者堆栈上的 basiclock 复制到 objectMonitor, // 同时保持 hashCode 的稳定。 // 如果 owner 决定在值为 0 时释放锁,则解锁将失败,控制最终将通过 slow_exit() 传递到 inflate。 // 然后 owner 将自旋,等待 0 值变化。 // 换句话说,如果 owner 碰巧在膨胀进行中尝试放弃锁(将 header 从 basiclock 恢复到对象), // 则 0 会导致 owner 中止。 // 该协议避免了竞争可能会导致的对象 hashCode 值更改或 "闪烁" 。 // 至关重要的是,虽然 object->mark 为 0,但 mark->displaced_mark_helper() 是稳定的。 // 0 用作 "BUSY" 指示器,表明正在进行膨胀。 // fetch the displaced mark from the owner's stack. // The owner can't die or unwind past the lock while our INFLATING // object is in the mark. Furthermore the owner can't complete // an unlock on the object, either. // 从 owner 线程的栈中获取 displaced mark // 当我们的 INFLATING 对象在 displaced mark 中时,owner 线程不能死亡或挂起。 // 此外,owner 也无法完成对对象的解锁。 markOop dmw = mark->displaced_mark_helper() ; assert (dmw->is_neutral(), "invariant") ; // Setup monitor fields to proper values -- prepare the monitor // 将监视器字段设置为适当的值 - 准备监视器 m->set_header(dmw) ; // Optimization: if the mark->locker stack address is associated // with this thread we could simply set m->_owner = Self and // m->OwnerIsThread = 1. Note that a thread can inflate an object // that it has stack-locked -- as might happen in wait() -- directly // with CAS. That is, we can avoid the xchg-NULL .... ST idiom. // 优化:如果 mark->locker 堆栈地址与该线程相关联, // 我们可以简单地设置 m->_owner = Self 和 m->OwnerIsThread = 1。 // 请注意,线程可以对已被堆栈锁定的对象进行膨胀——如可能发生在 wait() 中——直接使用 CAS。 // 也就是说,我们可以避免 xchg-NULL .... ST。 m->set_owner(mark->locker()); m->set_object(object); // TODO-FIXME: assert BasicLock->dhw != 0. // Must preserve store ordering. The monitor state must // be stable at the time of publishing the monitor address. // 必须保留存储顺序,监视器在发布其地址时状态必须保持稳定 guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ; object->release_set_mark(markOopDesc::encode(m)); // Hopefully the performance counters are allocated on distinct cache lines // to avoid false sharing on MP systems ... // 理想上,性能计数器们被分配在不同的缓存行上,来避免 MP 系统的错误共享 OM_PERFDATA_OP(Inflations, inc()); TEVENT(Inflate: overwrite stacklock); if (TraceMonitorInflation) { if (object->is_instance()) { ResourceMark rm; tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s", (void *) object, (intptr_t) object->mark(), object->klass()->external_name()); } } return m; } // CASE: neutral // 最后一种状态:无锁 // TODO-FIXME: for entry we currently inflate and then try to CAS _owner. // If we know we're inflating for entry it's better to inflate by swinging a // pre-locked objectMonitor pointer into the object header. A successful // CAS inflates the object *and* confers ownership to the inflating thread. // In the current implementation we use a 2-step mechanism where we CAS() // to inflate and then CAS() again to try to swing _owner from NULL to Self. // An inflateTry() method that we could call from fast_enter() and slow_enter() // would be useful. // 如果我们知道我们正在对处理的入口进行膨胀逻辑,更好的办法是通过偏转 // 预锁定的 objectMonitor 指针进入对象头。 // CAS 操作成功后,会膨胀对象并赋予膨胀线程所有权 // 在当前实现中,我们使用两步机制: // 1. CAS 操作进行膨胀 // 2. 再次 CAS 操作尝试偏转 _owner,使其从 NULL 转变为 Self // 在 fast_enter() 和 slow_enter() 中调用 inflateTry() 是一种有效的方法 assert (mark->is_neutral(), "invariant"); ObjectMonitor * m = omAlloc (Self); // prepare m for installation - set monitor to initial state // 初始化 objectMonitor m->Recycle(); m->set_header(mark); m->set_owner(NULL); m->set_object(object); // 这里 OwnerIsThread 不同了,表示 basicLock m->OwnerIsThread = 1 ; m->_recursions = 0 ; m->_Responsible = NULL ; // consider: keep metastats by type/class m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // 用CAS替换对象头的 mark word 为重量级锁状态 // 失败则重试 if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) { m->set_object (NULL) ; m->set_owner (NULL) ; m->OwnerIsThread = 0 ; m->Recycle() ; omRelease (Self, m, true) ; m = NULL ; continue ; // interference - the markword changed - just retry. // The state-transitions are one-way, so there's no chance of // live-lock -- "Inflated" is an absorbing state. } // Hopefully the performance counters are allocated on distinct // cache lines to avoid false sharing on MP systems ... OM_PERFDATA_OP(Inflations, inc()); TEVENT(Inflate: overwrite neutral); if (TraceMonitorInflation) { if (object->is_instance()) { ResourceMark rm; tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s", (void *) object, (intptr_t) object->mark(), object->klass()->external_name()); } } return m; } }
