Java中markword原理剖析

markword原理剖析


前言

本文旨在记录近期研读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 对象头主要由两部分组成:

  1. Mark Word :对应 C++ 中的 markOop
  2. 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),在编译期直接替换为几条极致的机器指令(如 ANDCMP)。

状态判断的源码实现

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 的设计哲学

  1. 零开销抽象 (Zero-cost Abstraction) :通过 C++ enum 替代 #define 宏。不仅具备宏替换在编译期的直接展开优势(无函数调用开销),还兼顾了编译期的类型检查和作用域限制。
  2. 指针二义性复用markOop 虽声明为指针(markOopDesc*),但它从不指向任何真实内存地址。它的指针值本身就是数据。这种将数据直接塞进指针变量(Tagged Pointer 思想)的巧妙设计,配合宏掩码操作,在解释器和 JIT 编译器中被高频使用,是 Java 并发性能的基石。