LockWord

LockWord 是 ART VM 中用来表示 Java 对象头(mirror::Object)里锁字段的一种紧凑编码方式。它把thin lock、fat lock、HashCode、forwarding address”等不同含义,通过一个 32 位整数里的若干比特域来区分,并且在同一个 32 位里还嵌入了“读屏障(read barrier)”状态和“标记位(mark bit)”这两种 GC 相关标志。

在Object类的实现中,有monitor的uint32_t成员

// The Class representing the type of the object.
  HeapReference<Class> klass_;
  // Monitor and hash code information.
  uint32_t monitor_;

这个monitor_成员实际上就会转化成LockWord进行管理,比如在Object类成员函数GetReadBarrierState

inline uint32_t Object::GetReadBarrierState() {
  if (!kUseBakerReadBarrier) {
    LOG(FATAL) << "Unreachable";
    UNREACHABLE();
  }
  DCHECK(kUseBakerReadBarrier);
  // 通过MonitorOffset得到偏移然后得到monitor_的字段,构造出LockWord
  LockWord lw(GetFieldPrimitive<uint32_t, /*kIsVolatile=*/false>(MonitorOffset())); 
  // 使用LockWord的ReadBarrierState成员函数
  uint32_t rb_state = lw.ReadBarrierState();
  DCHECK(ReadBarrier::IsValidReadBarrierState(rb_state)) << rb_state;
  return rb_state;
}

官方注释

LockWord的官方注释如下

/* The lock value itself as stored in mirror::Object::monitor_.  The two most significant bits
 * encode the state. The four possible states are fat locked, thin/unlocked, hash code, and
 * forwarding address.
 *
 * When the lock word is in the "thin" state and its bits are formatted as follows:
 *
 *  |33|2|2|222222221111|1111110000000000|
 *  |10|9|8|765432109876|5432109876543210|
 *  |00|m|r| lock count |thread id owner |
 *
 * The lock count is zero, but the owner is nonzero for a simply held lock.
 * When the lock word is in the "fat" state and its bits are formatted as follows:
 *
 *  |33|2|2|2222222211111111110000000000|
 *  |10|9|8|7654321098765432109876543210|
 *  |01|m|r| MonitorId                  |
 *
 * When the lock word is in hash state and its bits are formatted as follows:
 *
 *  |33|2|2|2222222211111111110000000000|
 *  |10|9|8|7654321098765432109876543210|
 *  |10|m|r| HashCode                   |
 *
 * When the lock word is in forwarding address state and its bits are formatted as follows:
 *
 *  |33|2|22222222211111111110000000000|
 *  |10|9|87654321098765432109876543210|
 *  |11|0| ForwardingAddress           |
 *
 * The `r` bit stores the read barrier state.
 * The `m` bit stores the mark bit state.
 */

总结

| 31 30 | 29 m| 28 r| 27 ...... 12  | 11 ........ 0 |
   ▲       ▲     ▲         ▲                 ▲
   │       │     │         │                 │
   │       │     │         │                 └— 低 12 位 存放thread id owner或者其他低位部分
   │       │     │         └— 用于不同状态下存放 lock count 或 HashCode MonitorId ForwardingAddress的高位部分
   │       │     └— `r`:一个比特,用于 ReadBarrier 状态(读屏障 GC 标志)
   │       └— `m`:一个比特,用于 MarkBit 状态(标记位,用于 GC) 
   └— 最高两位(bit 31-30):表示当前 lock word 的“状态类型”(共 4 种状态)

代码中体现为

enum SizeShiftsAndMasks : uint32_t {  // private marker to avoid generate-operator-out.py from processing.
    // Number of bits to encode the state, currently just fat or thin/unlocked or hash code.
    kStateSize = 2,
    kReadBarrierStateSize = 1,
    kMarkBitStateSize = 1,
    // Number of bits to encode the thin lock owner.
    kThinLockOwnerSize = 16,
    // Remaining bits are the recursive lock count. Zero means it is locked exactly once
    // and not recursively.
    kThinLockCountSize = 32 - kThinLockOwnerSize - kStateSize - kReadBarrierStateSize -
        kMarkBitStateSize,

    // Thin lock bits. Owner in lowest bits.
    kThinLockOwnerShift = 0,
    kThinLockOwnerMask = (1 << kThinLockOwnerSize) - 1,
    kThinLockOwnerMaskShifted = kThinLockOwnerMask << kThinLockOwnerShift,
    kThinLockMaxOwner = kThinLockOwnerMask,
    // Count in higher bits.
    kThinLockCountShift = kThinLockOwnerSize + kThinLockOwnerShift,
    kThinLockCountMask = (1 << kThinLockCountSize) - 1,
    kThinLockMaxCount = kThinLockCountMask,
    kThinLockCountOne = 1 << kThinLockCountShift,  // == 65536 (0x10000)
    kThinLockCountMaskShifted = kThinLockCountMask << kThinLockCountShift,

    // State in the highest bits.
    kStateShift = kReadBarrierStateSize + kThinLockCountSize + kThinLockCountShift +
        kMarkBitStateSize,
    kStateMask = (1 << kStateSize) - 1,
    kStateMaskShifted = kStateMask << kStateShift,
    kStateThinOrUnlocked = 0,
    kStateFat = 1,
    kStateHash = 2,
    kStateForwardingAddress = 3,
    kStateForwardingAddressShifted = kStateForwardingAddress << kStateShift,
    kStateForwardingAddressOverflow = (1 + kStateMask - kStateForwardingAddress) << kStateShift,

    // Read barrier bit.
    kReadBarrierStateShift = kThinLockCountSize + kThinLockCountShift,
    kReadBarrierStateMask = (1 << kReadBarrierStateSize) - 1,
    kReadBarrierStateMaskShifted = kReadBarrierStateMask << kReadBarrierStateShift,
    kReadBarrierStateMaskShiftedToggled = ~kReadBarrierStateMaskShifted,

    // Mark bit.
    kMarkBitStateShift = kReadBarrierStateSize + kReadBarrierStateShift,
    kMarkBitStateMask = (1 << kMarkBitStateSize) - 1,
    kMarkBitStateMaskShifted = kMarkBitStateMask << kMarkBitStateShift,
    kMarkBitStateMaskShiftedToggled = ~kMarkBitStateMaskShifted,

    // GC state is mark bit and read barrier state.
    kGCStateSize = kReadBarrierStateSize + kMarkBitStateSize,
    kGCStateShift = kReadBarrierStateShift,
    kGCStateMaskShifted = kReadBarrierStateMaskShifted | kMarkBitStateMaskShifted,
    kGCStateMaskShiftedToggled = ~kGCStateMaskShifted,

    // When the state is kHashCode, the non-state bits hold the hashcode.
    // Note Object.hashCode() has the hash code layout hardcoded.
    kHashShift = 0,
    kHashSize = 32 - kStateSize - kReadBarrierStateSize - kMarkBitStateSize,
    kHashMask = (1 << kHashSize) - 1,
    kMaxHash = kHashMask,

    // Forwarding address shift.
    kForwardingAddressShift = kObjectAlignmentShift,

    kMonitorIdShift = kHashShift,
    kMonitorIdSize = kHashSize,
    kMonitorIdMask = kHashMask,
    kMonitorIdAlignmentShift = 32 - kMonitorIdSize,
    kMonitorIdAlignment = 1 << kMonitorIdAlignmentShift,
    kMaxMonitorId = kMaxHash
  };

四种状态

“Thin/Unlocked” 状态(kStateThinOrUnlocked = 0)

当最高两位 = 00 时,表示是“薄锁”或者“Unlocked”或者“HashCode”状态中的一种,要结合 “m/r” 和“count/owner”位来判定。

Unlocked(完全未加锁,无 HashCode / 无 GC 标志)

  • 此时 全部为 0。
  • 最高两位 00,读屏障 bit = 0,标记 bit = 0,thin lock count = 0,owner = 0。
  • 任何 LockWord 实例默认构造(LockWord())就对应 “Unlocked” 状态。

Thin Lock(“单线程持有、无竞争”状态)

  • 最高两位同样是 00。
  • “m” 或 “r” bit 可能依然是 0(如果没参与并发 GC 情况)。
  • 低 16 位(kThinLockOwner)保存当前尝试加锁的线程 ID(线程 ID 实际上是一个 16 位以内的 ID)。
  • 接下来的 12 位(kThinLockCount)保存重入次数:当线程第一次 lock,count=0;如果同一个线程再次 lock,就 count++。
  • 例如:若线程 ID = 0x0012,且重入次数为 0,那么这一时刻 value_ 的二进制就是:
    [state=00][m=0][r=0][count=000000000000][owner=00000000010010]
    
  • 当 thin lock 发生竞争或需要 inflate 时,可能需要转为“Fat lock”状态。

HashCode(kStateHash = 2,两位最高为 10)

当一个对象第一次调用 hashCode(),且它从未加锁且无 hashcode,就会把 lock word 切换到 “HashCode” 状态。

  • 此时最高两位 10(即 kStateHash),GC 状态 bit(m/r)通常是 0,HashCode 本身放在低 30 位,具体占用 kHashSize = 32 − 2 − 1 − 1 = 28 位。
  • 也就是说 value_ = (hashCode << kHashShift) | (gc_bits << kGCStateShift) | (kStateHash << kStateShift)。
  • 其中 kHashShift = 0,即低 28 位直接存 hashCode & 0x0FFFFFFF。

取出 HashCode 的方法:

inline int32_t LockWord::GetHashCode() const {
  DCHECK_EQ(GetState(), kHashCode);
  CheckReadBarrierState();
  return (value_ >> kHashShift) & kHashMask;  // kHashShift = 0, kHashMask = (1<<28)-1
}
  • 当第一次调用 hashCode(),并且 LockWord 还是 value_ == 0(unlocked),就 CAS 将其改为 FromHashCode(computedHash, gc_bits=0)。之后再调用 hashCode 就直接读取 GetHashCode(),无需额外字典维护。
  • 如果后续需要加锁(ThinLock),就要把 HashCode 挪到Monitor中,或者在 ThinLock 状态里把 hash 放到 Monitor 里,“inflate”到FatLock,等解锁后把 hash 还原到 lock word。

Forwarding Address(kStateForwardingAddress = 3,两位最高为 11)

  • 在某些 GC(如复制式 GC、压缩 GC)时,对象被搬迁到新地址后,旧对象头要记录一个“转发地址”,以便后续其它引用可以更新引用指向。
  • 此时最高两位 = 11,读屏障和标记 bit 位为 0(因为 forwarding address 一般不会与读屏障/标记位共存)。
  • 剩下的 30 位里,真正存储的是:forwarding_address » kForwardingAddressShift。一般 kForwardingAddressShift = kObjectAlignmentShift,因为对象地址总是对齐到kObjectAlignmentShift,低几位都为 0,可以丢弃。

取出 forwarding address 的方法:

inline size_t LockWord::ForwardingAddress() const {
  DCHECK_EQ(GetState(), kForwardingAddress);
  return value_ << kForwardingAddressShift;
}

“Fat Lock”(kStateFat = 1,最高两位 01)

当 lock word 决定要“inflate”到胖锁(如出现竞争时),就把整 32 位转换为:

[ state = 01 ] [ markBit? ] [ readBarrier? ] [ MonitorId (30-bit) ]
  • 最高两位 01 表示这是一个“Fat Locked”状态;
  • 之后两位 “m/r” 可能顺便携带 GC 状态信息。
  • 余下的 30 位就是一个 “MonitorId”,该 ID 指向全局的一个 MonitorPool 中的某个 Monitor 对象。Monitor 结构包含:
    • 监视器所有者(owner),
    • 递归锁计数,
    • 等待在此 Monitor 上的线程队列(含条件变量)等实际同步数据,
    • 还有可能记录用于转入/转出等。

FatLockMonitor 的 C++ 实现如下:

inline Monitor* LockWord::FatLockMonitor() const {
  DCHECK_EQ(GetState(), kFatLocked);
  CheckReadBarrierState();
  MonitorId mon_id = (value_ >> kMonitorIdShift) & kMonitorIdMask;
  return MonitorPool::MonitorFromMonitorId(mon_id);
}
  • MonitorPool::MonitorFromMonitorId(mon_id) 会从全局 MonitorPool 中取出对应 ID 的 Monitor 对象指针。

注意:在 FatLock 状态下,“ThinLockOwner”与“ThinLockCount”域都已失效,把具体的锁定信息让给 Monitor 处理。

GC 相关:读屏障(ReadBarrier)与标记位(MarkBit)

在上述各状态布局中,还预留了两位来保存 GC 时的额外状态.

LockWord 提供了以下方法来获取/设置这两位:

uint32_t ReadBarrierState() const {
  return (value_ >> kReadBarrierStateShift) & kReadBarrierStateMask;
}
void SetReadBarrierState(uint32_t rb_state) {
  // 清掉 old bits
  value_ &= ~(kReadBarrierStateMask << kReadBarrierStateShift);
  // 或上新的 rb_state
  value_ |= (rb_state & kReadBarrierStateMask) << kReadBarrierStateShift;
}

uint32_t MarkBitState() const {
  return (value_ >> kMarkBitStateShift) & kMarkBitStateMask;
}
void SetMarkBitState(uint32_t mark_bit) {
  value_ &= kMarkBitStateMaskShiftedToggled;  // 清掉原来的 mark bit
  value_ |= mark_bit << kMarkBitStateShift;
}
  • 这些方法都会先 DCHECK 一下当前状态不是 forwarding address(即不允许在转发地址状态下修改 GC state)。

从 Thin 转到 Fat(inflate)

  • 正常无竞争加锁:第一次 monitorenter 时,ART 会尝试 CAS 将 “unlocked” (value_ = 0) 更新为 “ThinLock” 状态:即 thread_id << ownerShift) | (0 << countShift) | (state=0).
  • 重入:如果同一线程再次进入 synchronized,发现自己正是 owner,则仅把 “count” 增加 1。
  • 检测到竞争:如果某个线程看到 owner 不为自己,就要 inflate。
  • Inflate 的步骤:
    1. 创建或从 MonitorPool 中分配一个新的 Monitor。
    2. 构造 LockWord(Monitor*) 也就是 FatLock:把 state=01、MonitorId 写入;
    3. Monitor 结构里会把当前 ThinLock 的 owner 线程加入 Monitor 的 owner 字段,并且把待竞争线程 enqeue 到等待列表。
  • 释放锁:ThinLock 时,owner 线程退出同步块后,(count>0 ? count– : owner=0)。如果 owner 清零,就把 value_ 重置为“纯粹 unlocked + GC bits”状态。
  • 如果是 FatLock:调用 MonitorPool 里已分配好的 Monitor,Monitor 自己维护一个引用计数,当真正没人持有时再回收 Monitor 并把 LockWord 重置为 “unlocked”。