Android Runtime对象头结构解析(60)

73 阅读15分钟

码字不易,请大佬们点点关注,谢谢~

一、对象头概述

在Android Runtime(ART)中,每个Java对象都包含一个对象头(Object Header),它是对象在内存中的重要组成部分。对象头存储了与对象相关的元数据信息,这些信息对于垃圾回收、线程同步、哈希计算等操作至关重要。

从源码角度来看,对象头的定义和实现主要分布在art/runtime/mirror/object.hart/runtime/mark_word.h等文件中。对象头通常由多个字段组成,不同字段负责存储不同类型的元数据。接下来,我们将深入分析对象头的结构与实现细节。

二、对象头基础结构

2.1 对象头的组成

ART中的对象头主要由两部分组成:Mark Word和Class Pointer。Mark Word是一个机器字大小的字段,存储了与对象运行时状态相关的信息,如哈希码、锁状态、分代年龄等;Class Pointer指向对象的类元数据,用于在运行时确定对象的类型。

art/runtime/mirror/object.h中,对象的基本结构定义如下:

class Object {
 public:
  // 对象头的第一部分:Mark Word
  uintptr_t mark_;
  // 对象头的第二部分:Class Pointer
  mirror::Class* klass_;
  
  //...其他成员变量和方法
};

这里的mark_字段就是Mark Word,klass_字段是Class Pointer。

2.2 Mark Word的布局

Mark Word的布局根据不同的状态和平台有所变化。在32位系统和64位系统中,Mark Word的大小分别为32位和64位。在art/runtime/mark_word.h中,定义了Mark Word的各种状态和布局:

class MarkWord {
 public:
  // Mark Word的不同状态
  enum State {
    kStateUninitialized,  // 未初始化状态
    kStateMarked,         // 标记状态(用于垃圾回收)
    kStateWeaklyMarked,   // 弱标记状态(用于垃圾回收)
    kStateForwarded,      // 已转发状态(用于对象移动)
    kStateMonitor,        // 监视器状态(用于锁)
    kStateHash,           // 哈希码状态
    //...其他状态
  };
  
  // 获取Mark Word的当前状态
  State GetState() const;
  
  // 获取和设置各种信息的方法
  uint32_t GetHashCode() const;
  uint8_t GetAge() const;
  bool IsLocked() const;
  //...其他方法
};

Mark Word的布局会根据对象的状态动态变化,不同的状态下,各个位域用于存储不同的信息。例如,在无锁状态下,Mark Word可能存储对象的哈希码和分代年龄;在加锁状态下,Mark Word可能存储锁信息和指向锁记录的指针。

三、Mark Word详细解析

3.1 无锁状态下的Mark Word

在无锁状态下,Mark Word主要存储对象的哈希码和分代年龄。在art/runtime/mark_word.h中,无锁状态下Mark Word的布局如下:

// 无锁状态下Mark Word的布局(简化示例)
// [哈希码(25位) | 分代年龄(4位) | 锁标志位(3位)]
constexpr uintptr_t kHashMask = 0x01FFFFFF;      // 哈希码掩码
constexpr uintptr_t kAgeMask = 0x0000001E;       // 分代年龄掩码
constexpr uintptr_t kLockStateMask = 0x00000003; // 锁状态掩码

// 获取哈希码
uint32_t MarkWord::GetHashCode() const {
  return (value_ & kHashMask) >> kHashShift;
}

// 获取分代年龄
uint8_t MarkWord::GetAge() const {
  return (value_ & kAgeMask) >> kAgeShift;
}

// 判断是否为无锁状态
bool MarkWord::IsUnlocked() const {
  return (value_ & kLockStateMask) == kStateUnlocked;
}

在无锁状态下,Mark Word的低3位为锁标志位,表示对象处于无锁状态;接下来的4位存储对象的分代年龄,用于垃圾回收时判断对象是否应该晋升到老年代;剩余的高位存储对象的哈希码。

3.2 轻量级锁状态下的Mark Word

当对象被线程获取轻量级锁时,Mark Word的布局会发生变化。轻量级锁状态下,Mark Word存储指向线程栈中锁记录的指针。在art/runtime/mark_word.h中,轻量级锁状态下Mark Word的布局如下:

// 轻量级锁状态下Mark Word的布局(简化示例)
// [锁记录指针(61位) | 锁标志位(3位)]
constexpr uintptr_t kLockRecordPtrMask = 0xFFFFFFFFFFFFFFF8; // 锁记录指针掩码
constexpr uintptr_t kLockStateMask = 0x0000000000000007;     // 锁状态掩码

// 获取锁记录指针
uintptr_t MarkWord::GetLockRecordPtr() const {
  return (value_ & kLockRecordPtrMask) >> kLockRecordPtrShift;
}

// 判断是否为轻量级锁状态
bool MarkWord::IsLightweightLocked() const {
  return (value_ & kLockStateMask) == kStateLightweightLocked;
}

在轻量级锁状态下,Mark Word的低3位为锁标志位,表示对象处于轻量级锁状态;剩余的高位存储指向线程栈中锁记录的指针。锁记录中保存了对象原来的Mark Word值,以便在解锁时恢复。

3.3 重量级锁状态下的Mark Word

当多个线程竞争同一个对象的锁时,轻量级锁会膨胀为重量级锁。重量级锁状态下,Mark Word存储指向重量级锁(监视器)的指针。在art/runtime/mark_word.h中,重量级锁状态下Mark Word的布局如下:

// 重量级锁状态下Mark Word的布局(简化示例)
// [监视器指针(61位) | 锁标志位(3位)]
constexpr uintptr_t kMonitorPtrMask = 0xFFFFFFFFFFFFFFF8; // 监视器指针掩码
constexpr uintptr_t kLockStateMask = 0x0000000000000007; // 锁状态掩码

// 获取监视器指针
uintptr_t MarkWord::GetMonitorPtr() const {
  return (value_ & kMonitorPtrMask) >> kMonitorPtrShift;
}

// 判断是否为重量级锁状态
bool MarkWord::IsHeavyweightLocked() const {
  return (value_ & kLockStateMask) == kStateHeavyweightLocked;
}

在重量级锁状态下,Mark Word的低3位为锁标志位,表示对象处于重量级锁状态;剩余的高位存储指向重量级锁(监视器)的指针。监视器是一个对象,用于管理线程的同步和阻塞。

3.4 垃圾回收标记状态下的Mark Word

在垃圾回收过程中,Mark Word会被用于标记对象是否存活。在art/runtime/mark_word.h中,垃圾回收标记状态下Mark Word的布局如下:

// 垃圾回收标记状态下Mark Word的布局(简化示例)
// [标记位(1位) | 分代年龄(4位) | 锁标志位(3位)]
constexpr uintptr_t kMarkBitMask = 0x8000000000000000;   // 标记位掩码
constexpr uintptr_t kAgeMask = 0x000000000000001E;        // 分代年龄掩码
constexpr uintptr_t kLockStateMask = 0x0000000000000007;  // 锁状态掩码

// 获取标记位
bool MarkWord::IsMarked() const {
  return (value_ & kMarkBitMask) != 0;
}

// 设置标记位
void MarkWord::SetMarked(bool marked) {
  if (marked) {
    value_ |= kMarkBitMask;
  } else {
    value_ &= ~kMarkBitMask;
  }
}

在垃圾回收标记状态下,Mark Word的最高位为标记位,用于表示对象是否被标记为存活;接下来的4位仍然存储对象的分代年龄;低3位为锁标志位。

四、Class Pointer解析

4.1 Class Pointer的作用

Class Pointer是对象头的第二部分,它指向对象的类元数据。通过Class Pointer,运行时系统可以确定对象的类型,调用对象的方法,访问对象的静态字段等。在art/runtime/mirror/object.h中,Class Pointer的定义如下:

class Object {
 public:
  //...其他成员变量
  mirror::Class* klass_;
  
  // 获取对象的类
  mirror::Class* GetClass() const {
    return klass_;
  }
  
  // 设置对象的类
  void SetClass(mirror::Class* klass) {
    klass_ = klass;
  }
};

4.2 Class Pointer的实现细节

Class Pointer的实现细节在不同的平台和配置下可能有所不同。在64位系统中,为了节省内存,Class Pointer可能会使用压缩指针技术。在art/runtime/pointer_size.h中,定义了指针大小的相关配置:

// 指针大小配置
enum class PointerSize {
  k32Bits,  // 32位指针
  k64Bits,  // 64位指针
  k64BitsCompact,  // 64位压缩指针
};

// 获取Class Pointer的大小
PointerSize GetClassPointerSize() {
  // 根据运行时配置返回指针大小
  return kUseCompactReferences ? PointerSize::k64BitsCompact : PointerSize::k64Bits;
}

当使用压缩指针技术时,Class Pointer的实际存储值是经过压缩的,在使用时需要进行解压缩。在art/runtime/entrypoints/quick/quick_entrypoints.cc中,定义了解压缩Class Pointer的函数:

// 解压缩Class Pointer
mirror::Class* UncompressClassPointer(uintptr_t compressed_ptr) {
  // 根据压缩算法解压缩指针
  return reinterpret_cast<mirror::Class*>(compressed_ptr << kClassPointerShift);
}

五、对象头在垃圾回收中的作用

5.1 标记对象状态

在垃圾回收过程中,对象头的Mark Word用于标记对象的状态。在标记阶段,垃圾回收器从根集合开始遍历对象图,将可达对象的Mark Word标记为存活状态。在art/runtime/gc/collector/marksweep.cc中,标记对象的代码如下:

void MarkSweep::MarkObject(mirror::Object* obj) {
  if (obj == nullptr) {
    return;
  }
  
  // 获取对象的Mark Word
  MarkWord mark_word = obj->GetMarkWord();
  
  // 如果对象未被标记
  if (!mark_word.IsMarked()) {
    // 标记对象
    obj->SetMarkWord(mark_word.Mark());
    
    // 递归标记对象的引用
    ProcessReferences(obj);
  }
}

在这个过程中,Mark Word的标记位被设置为1,表示对象是存活的。

5.2 分代年龄管理

对象头的Mark Word还存储了对象的分代年龄。在年轻代垃圾回收过程中,每次对象经历一次垃圾回收而未被回收,其分代年龄就会增加。当分代年龄达到一定阈值时,对象会被晋升到老年代。在art/runtime/gc/collector/scavenger.cc中,处理对象晋升的代码如下:

void Scavenger::PromoteObject(mirror::Object* obj) {
  // 获取对象的分代年龄
  uint8_t age = obj->GetMarkWord().GetAge();
  
  // 如果分代年龄达到晋升阈值
  if (age >= kMaxScavengeNumber) {
    // 将对象晋升到老年代
    Heap* heap = Runtime::Current()->GetHeap();
    heap->PromoteToTenured(obj);
  } else {
    // 否则,增加对象的分代年龄
    MarkWord mark_word = obj->GetMarkWord();
    mark_word.SetAge(age + 1);
    obj->SetMarkWord(mark_word);
  }
}

5.3 对象移动与转发

在垃圾回收过程中,对象可能会被移动到新的内存位置。为了支持对象移动,对象头的Mark Word会被用于存储转发指针。在art/runtime/gc/collector/marksweep_compact.cc中,处理对象移动的代码如下:

void MarkSweepCompact::Compact() {
  // 计算对象的新位置
  CalculateNewObjectPositions();
  
  // 第一阶段:标记对象为已转发,并设置转发指针
  MarkAndForwardObjects();
  
  // 第二阶段:更新所有引用,使其指向对象的新位置
  UpdateReferences();
  
  // 第三阶段:移动对象到新位置
  MoveObjects();
}

void MarkSweepCompact::MarkAndForwardObjects() {
  // 遍历所有对象
  for (auto& obj : objects_) {
    if (obj->IsAlive()) {
      // 计算对象的新位置
      mirror::Object* new_obj = CalculateNewObjectPosition(obj);
      
      // 设置对象的Mark Word为转发状态,并存储转发指针
      MarkWord mark_word = obj->GetMarkWord();
      mark_word.SetForwarded(true);
      mark_word.SetForwardedPointer(new_obj);
      obj->SetMarkWord(mark_word);
    }
  }
}

在对象被移动后,任何对原对象的访问都会通过转发指针找到新对象。

六、对象头在线程同步中的作用

6.1 轻量级锁实现

对象头的Mark Word在轻量级锁实现中起着关键作用。当线程尝试获取对象的轻量级锁时,会通过CAS(Compare - And - Swap)操作将对象的Mark Word替换为指向锁记录的指针。在art/runtime/monitor.cc中,获取轻量级锁的代码如下:

bool Monitor::EnterLightweight(mirror::Object* obj) {
  Thread* self = Thread::Current();
  
  // 创建锁记录
  LockRecord* lock_record = self->AllocLockRecord();
  
  // 保存对象原来的Mark Word
  MarkWord old_mark = obj->GetMarkWord();
  
  // 检查对象是否处于无锁状态
  if (old_mark.IsUnlocked()) {
    // 构造新的Mark Word,包含锁记录指针
    MarkWord new_mark = MarkWord::FromLockRecord(lock_record);
    
    // 使用CAS操作尝试获取锁
    if (obj->CasMarkWord(old_mark, new_mark)) {
      // 获取锁成功
      lock_record->SetOwner(self);
      lock_record->SetObject(obj);
      return true;
    }
  }
  
  // 获取锁失败,可能需要膨胀为重量级锁
  return false;
}

6.2 重量级锁实现

当多个线程竞争同一个对象的锁时,轻量级锁会膨胀为重量级锁。在重量级锁实现中,对象头的Mark Word会指向一个监视器对象。在art/runtime/monitor.cc中,轻量级锁膨胀为重量级锁的代码如下:

void Monitor::Inflate(mirror::Object* obj, Thread* self) {
  // 创建重量级锁(监视器)
  Monitor* monitor = CreateHeavyweightMonitor();
  
  // 获取对象当前的Mark Word
  MarkWord mark_word = obj->GetMarkWord();
  
  // 检查对象是否仍处于轻量级锁状态
  if (mark_word.IsLightweightLocked()) {
    // 获取锁记录指针
    LockRecord* lock_record = mark_word.GetLockRecordPtr();
    
    // 将锁记录的信息转移到监视器
    monitor->InitFromLockRecord(lock_record);
    
    // 构造新的Mark Word,包含监视器指针
    MarkWord new_mark = MarkWord::FromMonitor(monitor);
    
    // 使用CAS操作将对象的Mark Word更新为指向监视器
    if (!obj->CasMarkWord(mark_word, new_mark)) {
      // CAS失败,说明对象状态已改变,需要重试
      Inflate(obj, self);
      return;
    }
  }
  
  // 其他处理...
}

6.3 偏向锁实现

偏向锁是一种针对单线程场景的优化锁机制。当对象第一次被线程获取锁时,会将该线程的ID记录在对象头的Mark Word中,此后该线程再次获取锁时无需进行任何同步操作。在art/runtime/monitor.cc中,偏向锁的实现代码如下:

bool Monitor::TryEnterBiased(mirror::Object* obj, Thread* self) {
  // 获取对象当前的Mark Word
  MarkWord mark_word = obj->GetMarkWord();
  
  // 检查对象是否处于无锁状态
  if (mark_word.IsUnlocked()) {
    // 构造新的Mark Word,包含线程ID
    MarkWord new_mark = MarkWord::FromBiasedThreadId(self->GetThreadId());
    
    // 使用CAS操作尝试设置偏向锁
    if (obj->CasMarkWord(mark_word, new_mark)) {
      // 设置偏向锁成功
      return true;
    }
  } else if (mark_word.IsBiased() && mark_word.GetBiasedThreadId() == self->GetThreadId()) {
    // 对象已经偏向当前线程,直接获取锁成功
    return true;
  }
  
  // 获取偏向锁失败
  return false;
}

七、对象头的其他功能

7.1 哈希码存储

对象的哈希码通常存储在对象头的Mark Word中。当对象的hashCode()方法第一次被调用时,会生成一个哈希码并存储在Mark Word中。在art/runtime/mirror/object-inl.h中,获取哈希码的代码如下:

uint32_t Object::GetHashCode() {
  // 获取对象的Mark Word
  MarkWord mark_word = GetMarkWord();
  
  // 检查Mark Word是否已经包含哈希码
  if (mark_word.HasHashCode()) {
    return mark_word.GetHashCode();
  }
  
  // 生成新的哈希码
  uint32_t hash_code = GenerateHashCode();
  
  // 尝试将哈希码存储到Mark Word中
  MarkWord new_mark = mark_word.SetHashCode(hash_code);
  if (CasMarkWord(mark_word, new_mark)) {
    return hash_code;
  }
  
  // CAS失败,说明Mark Word已被修改,重新获取
  return GetMarkWord().GetHashCode();
}

7.2 弱引用与软引用支持

对象头的Mark Word也用于支持弱引用和软引用。在垃圾回收过程中,垃圾回收器会根据对象头的状态判断对象是否被弱引用或软引用引用,从而决定是否回收该对象。在art/runtime/gc/collector/marksweep.cc中,处理弱引用的代码如下:

void MarkSweep::ProcessWeakReferences() {
  // 遍历所有弱引用
  for (auto& ref : weak_references_) {
    // 获取弱引用指向的对象
    mirror::Object* referent = ref->GetReferent();
    
    // 检查对象是否被标记为存活
    if (referent != nullptr && !referent->GetMarkWord().IsMarked()) {
      // 对象未被标记,说明不可达,将弱引用置为null
      ref->ClearReferent();
    }
  }
}

八、对象头的设计优化

8.1 空间优化

为了减少对象头占用的内存空间,ART采用了多种优化技术。例如,在64位系统中,使用压缩指针技术减少Class Pointer的大小;在Mark Word中,根据对象的不同状态,复用相同的位域存储不同的信息。在art/runtime/mark_word.h中,定义了各种状态下Mark Word的布局,通过复用位域来节省空间。

8.2 性能优化

对象头的设计也考虑了性能优化。例如,偏向锁和轻量级锁的实现减少了线程同步的开销;Mark Word的布局设计使得常见操作(如获取哈希码、检查锁状态)可以通过简单的位运算快速完成。在art/runtime/mirror/object-inl.h中,许多方法都使用了内联优化,减少了函数调用的开销。

九、对象头在不同场景下的应用与挑战

9.1 高并发场景

在高并发场景下,对象头的设计对性能有重要影响。频繁的锁竞争会导致轻量级锁膨胀为重量级锁,增加线程同步的开销。为了应对这一挑战,ART采用了偏向锁、轻量级锁等优化机制,减少无竞争情况下的锁开销。同时,对象头的CAS操作在高并发下可能会频繁失败,需要通过重试机制或锁粗化等技术来提高性能。

9.2 内存敏感场景

在内存敏感场景下,对象头占用的内存空间成为关注焦点。ART通过压缩指针、复用位域等技术减少对象头的内存占用。然而,这些优化可能会带来额外的计算开销,需要在内存使用和性能之间找到平衡点。例如,压缩指针的解压缩操作会增加一定的计算量,但可以显著减少内存使用。

十、对象头与其他ART组件的交互

10.1 与垃圾回收器的交互

对象头与垃圾回收器密切合作。在垃圾回收过程中,垃圾回收器通过对象头的Mark Word标记对象的存活状态、管理对象的分代年龄、处理对象的移动和转发等。在art/runtime/gc/collector目录下的各种垃圾回收器实现中,都有大量代码与对象头交互,以完成垃圾回收的各项任务。

10.2 与类加载系统的交互

对象头的Class Pointer指向对象的类元数据,这与类加载系统密切相关。当对象被创建时,需要通过类加载系统加载对象的类,并将Class Pointer指向对应的类元数据。在art/runtime/class_linker.cc中,实现了类加载和链接的过程,确保对象的Class Pointer正确指向其类元数据。

10.3 与JIT/AOT编译器的交互

对象头的设计也影响着JIT(Just - In - Time)和AOT(Ahead - Of - Time)编译器的优化。编译器可以利用对象头的信息进行类型推断、锁消除等优化。例如,在JIT编译过程中,如果编译器能够确定对象的锁状态,就可以进行锁消除优化,提高代码执行效率。在art/runtime/jitart/runtime/aot目录下的代码中,有多处与对象头相关的优化逻辑。