markword原理剖析
- 前言
- markword原理剖析
-
- [1. 核心头文件:oopsHierarchy.hpp 与 markOop.hpp](#1. 核心头文件:oopsHierarchy.hpp 与 markOop.hpp)
-
- [oopsHierarchy.hpp 源码片段](#oopsHierarchy.hpp 源码片段)
- [2. Mark Word 位分布与宏替换逻辑:markOop.hpp](#2. Mark Word 位分布与宏替换逻辑:markOop.hpp)
- [3. 锁状态常量的宏定义](#3. 锁状态常量的宏定义)
- [4. 运行时的宏逻辑替换与内联函数](#4. 运行时的宏逻辑替换与内联函数)
- [工程师视角:总结 HotSpot 的设计哲学](#工程师视角:总结 HotSpot 的设计哲学)
前言
本文旨在记录近期研读Java源码的学习心得与疑难问题。由于个人理解水平有限,文中内容难免存在疏漏,恳请读者不吝指正。
markword原理剖析
在OpenJDK 8的源码中,作为系统工程师,理解 Mark Word 的核心逻辑是掌握 Java 对象头、锁升级、垃圾回收(GC)标记以及线程同步的核心。
Mark Word 在 C++ 源码中并不是通过简单的 #define 宏来直接平铺直叙地定义所有状态,而是采用了一种高度复用的 "宏替换(Macro Replacement)" 与 "数据结构位移(Bit Shifting)" 结合 的设计模式。这样做的目的是为了在保证极其严苛的内存对齐(64位系统下通常为 8 字节)的同时,实现跨平台(x86, ARM, SPARC 等)的极致性能。
下面我们将深入到 OpenJDK 8 的核心源码中,分析其宏定义与宏替换的精妙逻辑。
1. 核心头文件:oopsHierarchy.hpp 与 markOop.hpp
在 HotSpot 虚拟机中,一切 Java 对象在 C++ 层面都继承自 oopDesc(Ordinary Object Pointer)。
Java 对象头主要由两部分组成:
- Mark Word :对应 C++ 中的
markOop。 - Klass Word :对应 C++ 中的
Klass*(或压缩指针narrowKlass)。
首先,我们看 markOop 是如何在 oopsHierarchy.hpp 中被宏定义和声明的。
oopsHierarchy.hpp 源码片段
cpp
// 路径:src/share/vm/oops/oopsHierarchy.hpp
// 强类型安全宏:根据编译环境(是否定义了CHECK_UNHANDLED_OOPS)来决定 oop 的声明方式
#ifdef CHECK_UNHANDLED_OOPS
// 如果开启了未处理指针检查,会将其定义为类对象,以提供编译期类型安全
class oop;
class markOop;
#else
// 生产环境(默认)下,通过 typedef 将其定义为指向特定结构体的指针
typedef class oopDesc* oop;
// 注意:markOop 实际上并不是一个真正的指针,它本质上是一个原生字长(uintptr_t)的数据
// 但为了与整个 oop 体系保持类型一致性,HotSpot 故意将其定义为指向 markOopDesc 的指针
typedef class markOopDesc* markOop;
#endif
2. Mark Word 位分布与宏替换逻辑:markOop.hpp
真正的魔法发生在 markOop.hpp。由于 Mark Word 在 32 位和 64 位系统下的位分布完全不同,且在不同锁状态(无锁、偏向锁、轻量级锁、重量级锁、GC标记)下,同一段内存空间代表完全不同的含义,HotSpot 在这里使用了大量的配置宏 和位掩码宏。
32位与64位的差异配置宏
cpp
// 路径:src/share/vm/oops/markOop.hpp
class markOopDesc: public oopDesc {
private:
// 返回当前的无符号长整型值
uintptr_t value() const { return (uintptr_t) this; }
public:
// 开头定义了基础的位宽常量(根据 _LP64 宏进行条件编译/宏替换)
enum {
#static_const_declarations
#ifdef _LP64
// 64位系统下的位偏移定义
age_bits = 4, // 分代年龄占 4 位
lock_bits = 2, // 锁标志位占 2 位
biased_lock_bits = 1, // 是否偏向锁标志占 1 位
max_hash_bits = bits_per_ext_oop - (age_bits + lock_bits + biased_lock_bits), // HashCode 占位
hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits, // 实际取 31 位
cms_bits = LP64_ONLY(1) NOT_LP64(0),
epoch_bits = 2 // 偏向锁时间戳(Epoch)占 2 位
#else
// 32位系统下的位偏移定义
age_bits = 4,
lock_bits = 2,
biased_lock_bits = 1,
max_hash_bits = bits_per_word - (age_bits + lock_bits + biased_lock_bits),
hash_bits = max_hash_bits,
cms_bits = 0,
epoch_bits = 2
#endif
};
核心状态宏替换与掩码计算
接下来,HotSpot 使用枚举(在 C++ 中通过枚举实现编译期常量宏替换)和位移运算,推导出用于掩码操作的十六进制常量:
cpp
// 路径:src/share/vm/oops/markOop.hpp(续)
enum {
// 1. 位移量(Shifts)计算
lock_shift = 0, // 锁标志从第 0 位开始
biased_lock_shift = lock_bits, // 偏向标志从第 2 位开始
age_shift = lock_bits + biased_lock_bits, // 年龄从第 3 位开始
cms_shift = age_shift + age_bits,
// 针对偏向锁和无锁状态的特殊位移
epoch_shift = cms_shift + cms_bits,
hash_shift = epoch_shift + epoch_bits, // HashCode 的起始位
// 2. 位掩码(Masks)宏定义:通过位移和减法动态生成掩码
// (1 << bits) - 1 可以完美生成低位全是 1 的掩码
lock_mask = right_n_bits(lock_bits), // 0x03
lock_mask_in_place = lock_mask << lock_shift, // 0x03
biased_lock_mask = right_n_bits(biased_lock_bits), // 0x01
biased_lock_mask_in_place = biased_lock_mask << biased_lock_shift, // 0x04
biased_lock_bit_in_place = 1 << biased_lock_shift, // 0x04 (偏向锁特征位)
age_mask = right_n_bits(age_bits), // 0x0F
age_mask_in_place = age_mask << age_shift, // 0x78 (3-6位)
epoch_mask = right_n_bits(epoch_bits), // 0x03
epoch_mask_in_place = epoch_mask << epoch_shift,
hash_mask = right_n_bits(hash_bits), // 31个1
hash_mask_in_place = hash_mask << hash_shift // 高位对应的 Hash 掩码
};
3. 锁状态常量的宏定义
HotSpot 并没有使用 #define 来固化锁的状态,而是使用了 C++ 内联枚举项来实现高效的宏替换,这在底层被称为 "In-place Constants"(就地常量):
cpp
// 路径:src/share/vm/oops/markOop.hpp(续)
enum {
locked_value = 0, // 00:轻量级锁(Lightweight Locked)
unlocked_value = 1, // 01:无锁状态(Unlocked)
monitor_value = 2, // 10:重量级锁 / 管程(Heavyweight Locked/Monitor)
marked_value = 3, // 11:GC 标记状态(Marked for GC)
biased_lock_pattern = 5 // 101:偏向锁状态(Biased Pattern, 包含 biased_lock_bit)
};
4. 运行时的宏逻辑替换与内联函数
在 JVM 运行期间,需要高频判断当前对象的锁状态。HotSpot 利用上述的枚举宏和位掩码,编写了大量的内联函数(Inlined Functions),在编译期直接替换为几条极致的机器指令(如 AND、CMP)。
状态判断的源码实现
cpp
// 路径:src/share/vm/oops/markOop.hpp(续)
// 1. 判断是否是偏向锁
bool is_biased() const {
// 逻辑:value() & 0x07 (biased_lock_mask_in_place | lock_mask_in_place)
// 结果如果等于 0x05 (biased_lock_pattern),则说明是偏向锁
return (value() & (biased_lock_mask_in_place | lock_mask_in_place)) == biased_lock_pattern;
}
// 2. 判断是否是轻量级锁
bool is_locked() const {
// 逻辑:如果最低两位(lock_mask_in_place = 0x03)不等于无锁状态(unlocked_value = 0x01)
// 理论上排除了无锁、偏向锁(偏向锁最低两位是 01,通过 is_biased 另行判断)
return (value() & lock_mask_in_place) != unlocked_value;
}
// 3. 判断是否是重量级锁(Monitor)
bool has_monitor() const {
// 逻辑:最低两位是否等于 0x02 (monitor_value)
return (value() & lock_mask_in_place) == monitor_value;
}
锁升级过程中的位替换逻辑
当一个对象需要从无锁 升级到轻量级锁 ,或者从偏向锁 撤销时,HotSpot 会直接在内存中构建一个新的 markOop 值,利用宏掩码清除旧数据并移入新数据:
cpp
// 创建一个无锁状态的 Mark Word 宏替换函数
static markOop prototype() {
// unlocked_value = 0x01,即产生一个最后两位是 01,其余位为 0 的 markOop
return markOop(uintptr_t(unlocked_value));
}
// 在当前 Mark Word 的基础上,通过宏掩码替换分代年龄(Age)
markOop set_has_monitor() {
// 强制将低两位擦除并替换为 monitor_value (0x02)
return markOop((value() & ~lock_mask_in_place) | monitor_value);
}
// 提取 HashCode 的宏替换逻辑
intptr_t hash() const {
assert(has_no_hash(), "must not have hash already");
// 先右移 hash_shift 位,再通过 hash_mask(低31位全是1)进行与操作,精确剥离出 HashCode
return (value() >> hash_shift) & hash_mask;
}
工程师视角:总结 HotSpot 的设计哲学
- 零开销抽象 (Zero-cost Abstraction) :通过 C++
enum替代#define宏。不仅具备宏替换在编译期的直接展开优势(无函数调用开销),还兼顾了编译期的类型检查和作用域限制。 - 指针二义性复用 :
markOop虽声明为指针(markOopDesc*),但它从不指向任何真实内存地址。它的指针值本身就是数据。这种将数据直接塞进指针变量(Tagged Pointer 思想)的巧妙设计,配合宏掩码操作,在解释器和 JIT 编译器中被高频使用,是 Java 并发性能的基石。