首先来看一下在内存中构造出相关数据结构之前,在Dex文件中是怎么定义一个类的。

Dex文件对类信息的组织

核心部件池

DEX 文件首先定义了几个大的“池”,里面存放了所有类都会用到的基础部件,实现了最大程度的数据复用。

  • string_ids (字符串池): 存放了文件中用到的所有字符串,例如类名 (“java.lang.String”)、字段名 (“value”)、方法名 (“equals”)、源文件名等。每个字符串只存一次。

  • type_ids (类型池): 存放了所有类型描述符,如 “Ljava/lang/String;” (类)、"[I" (int数组)、”V” (void)。它本身不存字符串,而是存一个指向 string_ids 的索引。

  • proto_ids (方法原型池): 存放了所有方法的签名(即参数列表和返回值类型)。例如,boolean equals(Object) 的原型就是 (Ljava/lang/Object;)Z。它通过指向 type_ids 的索引来描述每个类型。

  • field_ids (字段ID池): 定义了每一个字段的唯一标识。它由三部分索引组成:

    • 指向 type_ids 的索引,表示该字段所属的类。

    • 指向 type_ids 的索引,表示该字段的类型。

    • 指向 string_ids 的索引,表示该字段的名字。

  • method_ids (方法ID池): 定义了每一个方法的唯一标识。它也由三部分索引组成:

    • 指向 type_ids 的索引,表示该方法所属的类。

    • 指向 proto_ids 的索引,表示该方法的签名(参数和返回值)。

    • 指向 string_ids 的索引,表示该方法的名字。

类的定义

DEX 文件中有一个 class_defs 列表,其中每个 class_def_item 就代表一个在该 DEX 文件中定义的类。

一个 class_def_item 包含以下关键信息(大部分是索引):

  • class_idx: 指向 type_ids 的索引,说明“我是哪个类”(例如,Lcom/example/MyClass;)。

  • access_flags: 访问标志,如 public, final, interface 等。

  • superclass_idx: 指向 type_ids 的索引,说明“我的父类是谁”。

  • interfaces_off: 一个偏移量,指向一个列表,该列表包含了这个类实现的所有接口(每个接口也是一个指向 type_ids 的索引)。

  • source_file_idx: 指向 string_ids 的索引,说明“我的源文件名是什么”。

  • annotations_off: 一个偏移量,指向这个类的注解信息。

  • class_data_off: 指向了真正定义类成员(字段和方法)的数据区。

  • static_values_off: 一个偏移量,指向静态字段的初始值列表。

类的成员列表

class_def_item 中的 class_data_off 指向一个 class_data_item。这里才是真正“组装”类的地方。一个 class_data_item 的结构如下:

  • 头信息
    • static_fields_size: 静态字段的数量。

    • instance_fields_size: 实例字段的数量。

    • direct_methods_size: 直接方法(私有方法、静态方法、构造方法)的数 量。

    • virtual_methods_size: 虚方法(除了static final 构造函数所有其他方 法)的数量。

  • 成员列表

    • static_fields 列表: 每一项都包含一个指向 field_ids 的索引以及访问标志

    • instance_fields 列表: 同上。

    • direct_methods 列表: 每一项都包含一个指向 method_ids 的索引(指明是哪个方法)以及访问标志,加上一个偏移量,指向该方法的实际字节码 (code_item)。

    • virtual_methods 列表: 同上。

DexCache数据结构

DexCache 是与单个 DexFile 紧密绑定的一个运行时缓存。缓存已经从 .dex 文件中解析(Resolve)过的字符串、类型、字段和方法。它的存在是为了避免重复解析 .dex 文件中那些基于索引的定义,从而大幅提升性能。

final class DexCache {
    private ClassLoader classLoader;
    private String location;
    /** Holds C pointer to dexFile. */
    @UnsupportedAppUsage
    private long dexFile;
    private long resolvedCallSites;
    private long resolvedFields;
    private long resolvedFieldsArray;
    private long resolvedMethodTypes;
    private long resolvedMethodTypesArray;
    private long resolvedMethods;
    private long resolvedMethodsArray;

    private long resolvedTypes;
    private long resolvedTypesArray;
    private long strings;
    private long stringsArray;
    private DexCache() {}
}

对应的C++ mirror定义

// C++ mirror of java.lang.DexCache.
class MANAGED DexCache final : public Object {
  // ......
  HeapReference<ClassLoader> class_loader_;
  HeapReference<String> location_;

  uint64_t dex_file_;                     // const DexFile*
                                          //
  uint64_t resolved_call_sites_;          // Array of call sites
  uint64_t resolved_fields_;              // NativeDexCacheArray holding ArtField's
  uint64_t resolved_fields_array_;        // Array of ArtField's.
  uint64_t resolved_method_types_;        // DexCacheArray holding mirror::MethodType's
  uint64_t resolved_method_types_array_;  // Array of mirror::MethodType's
  uint64_t resolved_methods_;             // NativeDexCacheArray holding ArtMethod's
  uint64_t resolved_methods_array_;       // Array of ArtMethod's
  uint64_t resolved_types_;               // DexCacheArray holding mirror::Class's
  uint64_t resolved_types_array_;         // Array of resolved types.
  uint64_t strings_;                      // DexCacheArray holding mirror::String's
  uint64_t strings_array_;                // Array of String's.
};

这里的uint64_t类型实际上都是存储原生指针

resolved_methods_ 和 resolved_methods_array_两个字段缓存已解析的方法。当 ART 需要调用一个在 .dex 文件中索引为 i 的方法时,它会首先检查这个缓存。如果缓存中已有对应的 ArtMethod*,就直接使用;如果没有,ART 会解析方法,创建 ArtMethod 对象,然后将其指针存入缓存中,供下次使用。

为什么有**array_2个字段呢?

在commit message 中提到

如果需要缓存的条目数量很多,那么NativeDexCacheArray的管理优势就能体现出来。ART 仍会使用旧的、更复杂的结构,并将指针存放在原始字段中(例如 resolved_methods_)。

如果一个 .dex 文件需要缓存的条目数量很少(低于某个设定的阈值),ART 就会选择使用这个轻便的数组。它会分配一个纯数组,并将指针存放在新增的 array 字段中(例如 resolved_methods_array_)。也意味着 nterp 的汇编代码可以用几条最基础、最快的机器指令来直接计算出缓存项的地址并读取它。

在旧版本中,DexCache 对每种需要缓存的实体(方法、字段、类型等)都只用一种缓存结构 NativeDexCacheArray。

DexCachePair / NativeDexCachePair

template <typename T> struct alignas(8) DexCachePair {
    GcRoot<T> object; // 缓存的对象
    uint32_t index;   // 原始的DEX文件索引
};

DexCachePairArray 利用 DexCachePair 实现了一个哈希表,用于解决“用小空间缓存大范围索引”的问题。

当要缓存一个索引为 idx 的条目时,它并不直接存在数组的第 idx 位。相反,它通过取模运算 slot = idx % size 计算出一个“槽位”。

因为多个不同的 idx 可能映射到同一个 slot(哈希冲突),所以每次查找时,除了计算 slot,还必须进行一次关键的比较:if (idx != a_pair.index)。只有当要查找的 idx 和槽位中存储的 index 完全一致时,才证明找到了正确的条目。

访问开销稍高。每次访问都需要一次取模运算和一次索引比较,比直接访问数组要慢一些。

GcRootArray / NativeArray

template <typename T> class GcRootArray {
private:
    Atomic<GcRoot<T>> entries_[0]; // 变长数组技巧
};

当要查找索引为 idx 的条目时,它直接访问数组的第 idx 个元素 entries_[idx]。访问速度极快、可能浪费内存。

DEFINE_DUAL_CACHE决策使用哪个方案

// NOLINTBEGIN(bugprone-macro-parentheses)
#define DEFINE_ARRAY(name, array_kind, getter_setter, type, ids, alloc_kind) \
  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
  array_kind* Get ##getter_setter() \
      ALWAYS_INLINE \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    return GetFieldPtr<array_kind*, kVerifyFlags>(getter_setter ##Offset()); \
  } \
  void Set ##getter_setter(array_kind* value) \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    SetFieldPtr<false>(getter_setter ##Offset(), value); \
  } \
  static constexpr MemberOffset getter_setter ##Offset() { \
    return OFFSET_OF_OBJECT_MEMBER(DexCache, name); \
  } \
  array_kind* Allocate ##getter_setter(bool startup = false) \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    return reinterpret_cast<array_kind*>(AllocArray<type>( \
        getter_setter ##Offset(), GetDexFile()->ids(), alloc_kind, startup)); \
  } \
  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
  size_t Num ##getter_setter() REQUIRES_SHARED(Locks::mutator_lock_) { \
    return Get ##getter_setter() == nullptr ? 0u : GetDexFile()->ids(); \
  } \

#define DEFINE_PAIR_ARRAY(name, pair_kind, getter_setter, type, size, alloc_kind) \
  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
  pair_kind ##Array<type, size>* Get ##getter_setter() \
      ALWAYS_INLINE \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    return GetFieldPtr<pair_kind ##Array<type, size>*, kVerifyFlags>(getter_setter ##Offset()); \
  } \
  void Set ##getter_setter(pair_kind ##Array<type, size>* value) \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    SetFieldPtr<false>(getter_setter ##Offset(), value); \
  } \
  static constexpr MemberOffset getter_setter ##Offset() { \
    return OFFSET_OF_OBJECT_MEMBER(DexCache, name); \
  } \
  pair_kind ##Array<type, size>* Allocate ##getter_setter() \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    return reinterpret_cast<pair_kind ##Array<type, size>*>( \
        AllocArray<std::atomic<pair_kind<type>>>( \
            getter_setter ##Offset(), size, alloc_kind)); \
  } \
  template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags> \
  size_t Num ##getter_setter() REQUIRES_SHARED(Locks::mutator_lock_) { \
    return Get ##getter_setter() == nullptr ? 0u : size; \
  } \

#define DEFINE_DUAL_CACHE( \
    name, pair_kind, getter_setter, type, pair_size, alloc_pair_kind, \
    array_kind, component_type, ids, alloc_array_kind) \
  DEFINE_PAIR_ARRAY( \
      name, pair_kind, getter_setter, type, pair_size, alloc_pair_kind) \
  DEFINE_ARRAY( \
      name ##array_, array_kind, getter_setter ##Array, component_type, ids, alloc_array_kind) \
  type* Get ##getter_setter ##Entry(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) { \
    DCHECK_LT(index, GetDexFile()->ids()); \
    auto* array = Get ##getter_setter ##Array(); \
    if (array != nullptr) { \
      return array->Get(index); \
    } \
    auto* pairs = Get ##getter_setter(); \
    if (pairs != nullptr) { \
      return pairs->Get(index); \
    } \
    return nullptr; \
  } \
  void Set ##getter_setter ##Entry(uint32_t index, type* resolved) \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    DCHECK_LT(index, GetDexFile()->ids()); \
    auto* array = Get ##getter_setter ##Array(); \
    if (array != nullptr) { \
      array->Set(index, resolved); \
    } else { \
      auto* pairs = Get ##getter_setter(); \
      if (pairs == nullptr) { \
        bool should_allocate_full_array = ShouldAllocateFullArray(GetDexFile()->ids(), pair_size); \
        if (ShouldAllocateFullArrayAtStartup() || should_allocate_full_array) { \
          array = Allocate ##getter_setter ##Array(!should_allocate_full_array); \
          array->Set(index, resolved); \
        } else { \
          pairs = Allocate ##getter_setter(); \
          pairs->Set(index, resolved); \
        } \
      } else { \
        pairs->Set(index, resolved); \
      } \
    } \
  } \
  void Unlink ##getter_setter ##ArrayIfStartup() \
      REQUIRES_SHARED(Locks::mutator_lock_) { \
    if (!ShouldAllocateFullArray(GetDexFile()->ids(), pair_size)) { \
      Set ##getter_setter ##Array(nullptr) ; \
    } \
  }

  DEFINE_ARRAY(resolved_call_sites_,
               GcRootArray<CallSite>,
               ResolvedCallSites,
               GcRoot<CallSite>,
               NumCallSiteIds,
               LinearAllocKind::kGCRootArray)

  DEFINE_DUAL_CACHE(resolved_fields_,
                    NativeDexCachePair,
                    ResolvedFields,
                    ArtField,
                    kDexCacheFieldCacheSize,
                    LinearAllocKind::kNoGCRoots,
                    NativeArray<ArtField>,
                    ArtField*,
                    NumFieldIds,
                    LinearAllocKind::kNoGCRoots)

  DEFINE_DUAL_CACHE(resolved_method_types_,
                    DexCachePair,
                    ResolvedMethodTypes,
                    mirror::MethodType,
                    kDexCacheMethodTypeCacheSize,
                    LinearAllocKind::kDexCacheArray,
                    GcRootArray<mirror::MethodType>,
                    GcRoot<mirror::MethodType>,
                    NumProtoIds,
                    LinearAllocKind::kGCRootArray);

  DEFINE_DUAL_CACHE(resolved_methods_,
                    NativeDexCachePair,
                    ResolvedMethods,
                    ArtMethod,
                    kDexCacheMethodCacheSize,
                    LinearAllocKind::kNoGCRoots,
                    NativeArray<ArtMethod>,
                    ArtMethod*,
                    NumMethodIds,
                    LinearAllocKind::kNoGCRoots)

  DEFINE_DUAL_CACHE(resolved_types_,
                    DexCachePair,
                    ResolvedTypes,
                    mirror::Class,
                    kDexCacheTypeCacheSize,
                    LinearAllocKind::kDexCacheArray,
                    GcRootArray<mirror::Class>,
                    GcRoot<mirror::Class>,
                    NumTypeIds,
                    LinearAllocKind::kGCRootArray);

  DEFINE_DUAL_CACHE(strings_,
                    DexCachePair,
                    Strings,
                    mirror::String,
                    kDexCacheStringCacheSize,
                    LinearAllocKind::kDexCacheArray,
                    GcRootArray<mirror::String>,
                    GcRoot<mirror::String>,
                    NumStringIds,
                    LinearAllocKind::kGCRootArray);

宏展开后的代码长成这样

//======= 由 DEFINE_PAIR_ARRAY(resolved_fields_, ...) 展开而来 =======
// 
// 这一部分定义了哈希表方案的访问和分配函数。
//
// 哈希表的 size 由 kDexCache*CacheSize确定,默认都是1024
// template<...> pair_kind##Array<type, size>* Get##getter_setter()
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
NativeDexCachePairArray<ArtField, 1024>* GetResolvedFields()
    ALWAYS_INLINE
    REQUIRES_SHARED(Locks::mutator_lock_) {
  return GetFieldPtr<NativeDexCachePairArray<ArtField, 1024>*, kVerifyFlags>(ResolvedFieldsOffset());
}

// void Set##getter_setter(pair_kind##Array<type, size>* value)
void SetResolvedFields(NativeDexCachePairArray<ArtField, 1024>* value)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  SetFieldPtr<false>(ResolvedFieldsOffset(), value);
}

// static constexpr MemberOffset getter_setter##Offset()
static constexpr MemberOffset ResolvedFieldsOffset() {
  return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_fields_);
}

// pair_kind##Array<type, size>* Allocate##getter_setter()
NativeDexCachePairArray<ArtField, 1024>* AllocateResolvedFields()
    REQUIRES_SHARED(Locks::mutator_lock_) {
  return reinterpret_cast<NativeDexCachePairArray<ArtField, 1024>*>(
      AllocArray<std::atomic<NativeDexCachePair<ArtField>>>(
          ResolvedFieldsOffset(), 1024, LinearAllocKind::kNoGCRoots));
}

// template<...> size_t Num##getter_setter()
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
size_t NumResolvedFields() REQUIRES_SHARED(Locks::mutator_lock_) {
  return GetResolvedFields() == nullptr ? 0u : 1024;
}

//======= 由 DEFINE_ARRAY(resolved_fields_array_, ...) 展开而来 =======
//
// 这一部分定义了“文件柜”(纯数组)方案的访问和分配函数。
//

// template<...> array_kind* Get##getter_setter()
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
NativeArray<ArtField>* GetResolvedFieldsArray()
    ALWAYS_INLINE
    REQUIRES_SHARED(Locks::mutator_lock_) {
  return GetFieldPtr<NativeArray<ArtField>*, kVerifyFlags>(ResolvedFieldsArrayOffset());
}

// void Set##getter_setter(array_kind* value)
void SetResolvedFieldsArray(NativeArray<ArtField>* value)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  SetFieldPtr<false>(ResolvedFieldsArrayOffset(), value);
}

// static constexpr MemberOffset getter_setter##Offset()
static constexpr MemberOffset ResolvedFieldsArrayOffset() {
  return OFFSET_OF_OBJECT_MEMBER(DexCache, resolved_fields_array_);
}

// array_kind* Allocate##getter_setter(bool startup = false)
NativeArray<ArtField>* AllocateResolvedFieldsArray(bool startup = false)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  return reinterpret_cast<NativeArray<ArtField>*>(AllocArray<ArtField*>(
      ResolvedFieldsArrayOffset(), GetDexFile()->NumFieldIds(), LinearAllocKind::kNoGCRoots, startup));
}

// template<...> size_t Num##getter_setter()
template<VerifyObjectFlags kVerifyFlags = kDefaultVerifyFlags>
size_t NumResolvedFieldsArray() REQUIRES_SHARED(Locks::mutator_lock_) {
  return GetResolvedFieldsArray() == nullptr ? 0u : GetDexFile()->NumFieldIds();
}

//======= 由 DEFINE_DUAL_CACHE 直接定义的函数展开而来 =======
//
// 这一部分提供了统一的 Get/Set 接口,内部包含了智能决策逻辑。
//

// type* Get##getter_setter##Entry(uint32_t index)
ArtField* GetResolvedFieldsEntry(uint32_t index) REQUIRES_SHARED(Locks::mutator_lock_) {
  DCHECK_LT(index, GetDexFile()->NumFieldIds());
  // 优先使用“纯数组”方案
  auto* array = GetResolvedFieldsArray();
  if (array != nullptr) {
    return array->Get(index);
  }
  // 否则,使用“哈希表”方案
  auto* pairs = GetResolvedFields();
  if (pairs != nullptr) {
    return pairs->Get(index);
  }
  return nullptr;
}

// void Set##getter_setter##Entry(uint32_t index, type* resolved)
void SetResolvedFieldsEntry(uint32_t index, ArtField* resolved)
    REQUIRES_SHARED(Locks::mutator_lock_) {
  DCHECK_LT(index, GetDexFile()->NumFieldIds());
  // 优先写入“纯数组”
  auto* array = GetResolvedFieldsArray();
  if (array != nullptr) {
    array->Set(index, resolved);
  } else {
    // 如果“纯数组”不存在,再检查“哈希表”
    auto* pairs = GetResolvedFields();
    if (pairs == nullptr) {
      // 如果两者都不存在(首次写入),则进行决策
      bool should_allocate_full_array = ShouldAllocateFullArray(GetDexFile()->NumFieldIds(), 1024);
      if (ShouldAllocateFullArrayAtStartup() || should_allocate_full_array) {
        // 决策结果:使用“纯数组”
        array = AllocateResolvedFieldsArray(!should_allocate_full_array);
        array->Set(index, resolved);
      } else {
        // 决策结果:使用“哈希表”
        pairs = AllocateResolvedFields();
        pairs->Set(index, resolved);
      }
    } else {
      // 如果“哈希表”已存在,则直接写入
      pairs->Set(index, resolved);
    }
  }
}

// void Unlink##getter_setter##ArrayIfStartup()
void UnlinkResolvedFieldsArrayIfStartup()
    REQUIRES_SHARED(Locks::mutator_lock_) {
  if (!ShouldAllocateFullArray(GetDexFile()->NumFieldIds(), 1024)) {
    SetResolvedFieldsArray(nullptr) ;
  }
}

DEFINE_DUAL_CACHE 这个宏为每种缓存类型(resolved_fields, resolved_methods 等)生成了一套统一的逻辑。

  • 首先检查 *_array_ 是否已经被分配了。如果已经分配,就直接使用它。

  • 如果未分配,再检查 哈希表方案 是否已被分配。如果已经分配,就使用它。

  • 如果两者都未分配(这是第一次为该类型的缓存设置条目),则进入决策逻辑:

    • ShouldAllocateFullArray 会比较这个 DEX 文件中该类型条目的总数(GetDexFile()->ids())和设置的哈希表大小。
    • 如果总数 <= 哈希表大小: 说明这是个小 DEX 文件,于是分配一个纯数组 (Allocate…Array)。
    • 如果总数 > 哈希表大小: 说明这是个大 DEX 文件,于是分配一个哈希表 (Allocate…)。

ArtField数据结构

class EXPORT ArtField final {
  // ... ...
  // 指向定义了这个字段的 mirror::Class 对象。
  GcRoot<mirror::Class> declaring_class_;

  // 访问标志
  uint32_t access_flags_ = 0;

  //该字段在 .dex 文件 field_ids 池中的索引
  // Dex cache index of field id
  uint32_t field_dex_idx_ = 0;

  // 对于实例字段  这个 offset_ 是指该字段相对于其所属对象实例起始地址的字节偏移。
  // 对于静态字段 这个 offset_ 是指该字段相对于其所属类对象起始地址的字节偏移。
  // Offset of field within an instance or in the Class' static fields
  uint32_t offset_ = 0;
};

ArtField 是 Java 字段在 ART 内存中的 C++ 表示。它本身不存储字段的值,而是告诉虚拟机去哪里找到这个字段的值。

ArtMethod数据结构

class EXPORT ArtMethod final {
 protected:
  // 指向定义了这个方法的 mirror::Class 对象
  GcRoot<mirror::Class> declaring_class_;
  
  // 在类被链接后,虚拟机的不同组件(如 JIT 编译器、校验器)可能会在多线程环境中并发地修改
  // 这些标志位。例如,JIT 可能会把它标记为“已编译”,或者一个优化可能会把它标记为“单实现方
  // 法”。使用原子类型确保了这些并发修改是线程安全的。
  std::atomic<std::uint32_t> access_flags_;
  
  // 一个整数,表示这个方法在原始 .dex 文件 method_ids 池中的索引
  uint32_t dex_method_index_;
  
  // 方法在其所属类的方法列表中的索引。
  // 对于静态方法或私有方法,它是 directMethods 数组的索引。
  // 对于虚方法,它是 vtable(虚方法表)的索引。
  // 对于接口方法,它是实现类的 IfTable(接口方法表)中对应接口方法数组的索引。
  uint16_t method_index_;
  
  union {
    // 对于普通方法,这里存储的是它的热度计数值。当一个方法被频繁调用时,ART 会增加这个计
    // 数。JIT 编译器会周期性地检查这个值,当它超过某个阈值时,JIT 就会将这个“热点方法”
    // 编译成高质量的机器码以提升性能。注释中提到它不是原子的,因为偶尔丢失一两次计数不影
    // 响最终识别出热点方法,这是一种性能上的权衡。
    uint16_t hotness_count_;
    // 对于抽象的接口方法,这里存储的是它在 IMT (Interface Method Table) 中的索引,这是
    //  ART 用于加速接口方法调用的另一项优化。
    uint16_t imt_index_;
  };
  
  // Fake padding field gets inserted here.
  
  // Must be the last fields in the method.
  struct PtrSizedFields {
    // Native 方法: 指向已注册的 C/C++ JNI 函数地址。
    // 需要解析的方法 (Resolution Method): 指向一个运行时的内部函数,该函数负责在首次调
    // 用时解析出真正的目标方法。
    // 冲突方法 (Conflict Method): 当接口方法表(IMT)发生哈希冲突时,它指向一个
    // ImtConflictTable 结构,用于解决冲突。
    // 抽象/接口方法: 如果 ART 的静态分析发现这个抽象方法在整个应用中只有一个实现,这里会
    // 直接指向那个唯一的实现方法,将一个动态调用优化为直接调用。
    // 普通方法: 在 AOT 编译时,它存储的是方法字节码 (CodeItem) 在 .dex 文件中的偏移量。
    // 在 运行时,这个偏移量会被“修复”为指向内存中 CodeItem 的直接指针。
    void* data_;
  
    // 当 AOT/JIT 编译后的代码要调用这个方法时,就会跳转到这个指针所指向的地址。
    // 它不一定总是指向 AOT 代码。例如,如果方法需要被解释执行,这个指针就会指向 
    // quick_to_interpreter_bridge_trampoline,从而将执行流引导到解释器。
    void* entry_point_from_quick_compiled_code_;
  } ptr_sized_fields_;
};

Class 数据结构

class EXPORT MANAGED Class final : public Object {
  // 指向加载了这个类的 ClassLoader。如果是启动类加载器,则为 null
  HeapReference<ClassLoader> class_loader_;
  // 数组元素类型: 仅对数组类有效。普通类此字段为 null。
  HeapReference<Class> component_type_;
  // 指向这个类所属的 DexFile 对应的 DexCache。虚拟机通过它来快速解析方法、字段等。
  HeapReference<DexCache> dex_cache_;
  // 一个指向 ClassExt 对象的指针,用于存储一些不常用或“额外”的数据,比如校验和错误信息。
  // 这个字段是懒分配的,只有在需要时才会创建,以节省内存
  HeapReference<ClassExt> ext_data_;
  // 因为 Java 支持实现多个接口,无法用单一的 vtable 来处理。iftable_ 存储了一系列的键值
  // 对,每个键是一个接口的 Class 对象,值是另一个指针数组,该数组包含了当前类对这个接口所
  // 有方法的具体实现(ArtMethod*)。
  HeapReference<IfTable> iftable_;
  // Descriptor 字符串,懒加载。
  HeapReference<String> name_;
  // 指向该类的直接父类。
  HeapReference<Class> super_class_;
  // 这是一个指针数组,包含了所有可以被子类重写(override)的虚方法的 ArtMethod 指针。
  // 当执行 invoke-virtual 指令时,通过对象的 vtable_ 快速找到并调用正确的方法实现。
  // 它从父类拷贝并根据当前类的方法进行修改。
  HeapReference<PointerArray> vtable_;
  // 指向一个 ArtField 的数组,包含了该类直接声明的所有静态和实例字段。
  // 父类的字段不在这里,而在父类的 fields_ 中
  uint64_t fields_;
  // 方法列表: 指向一个 ArtMethod 的数组,包含了该类逻辑上定义的所有方法。
  // 这是一个非常重要的字段,其内部布局被精心划分为三个部分:
  // [0, virtual_methods_offset_): 直接方法。包括静态方法、私有方法和构造函数.
  // [virtual_methods_offset_, copied_methods_offset_): 
  // 虚方法即该类声明的 public 或 protected 的非 final 方法。
  // [copied_methods_offset_, ...): 
  // 拷贝方法。主要指为了实现接口而从父接口中拷贝过来的默认方法 (default method) 
  // 或 Miranda methods (为了让抽象父类满足接口要求而隐式生成的具体方法)。
  uint64_t methods_;
  // 访问标志
  uint32_t access_flags_;
  //ART 内部使用的一些帮助 GC 等子系统快速判断的标志。
  uint32_t class_flags_;
  // Class 对象自身的大小: mirror::Class 对象本身在堆上占用的总字节数。
  uint32_t class_size_;
  // 当一个线程开始初始化这个类时(执行 <clinit>),会把自己的线程ID记录在这里
  // 用于检测多线程环境下的循环初始化错误
  pid_t clinit_thread_id_;
  // 将这个内存中的 Class 对象链接回它在原始 .dex 文件中定义的索引
  int32_t dex_class_def_idx_;
  int32_t dex_type_idx_;
  // 记录了实例/静态字段中引用类型的数量
  uint32_t num_reference_instance_fields_;
  uint32_t num_reference_static_fields_;
  // 实例对象的大小
  uint32_t object_size_;
  // 当一个类满足某些条件时,ART 会将其实例大小缓存到这个字段中。
  // GC 在分配这个类的实例时,可以直接读取这个字段,走一个“快速分配路径”,
  // 避免了更复杂的尺寸计算和状态检查,从而提升对象创建的速度。
  uint32_t object_size_alloc_fast_path_;
  // 低 16 位: 存储一个 Primitive::Type 枚举值,表示这是哪种原始类型
  // 高 16 位: 存储该原始类型尺寸的位移值 (size shift)。例如,int 是 4 字节位移值就是 2
  uint32_t primitive_type_;
  // 实例引用字段偏移量的位图 (Bitmap)。
  // GC 在扫描一个对象时,需要知道哪些字段是指向其他对象的引用 (指针)。
  // 它的每一位 (bit) 对应一个 32 位的内存“槽位”。
  // 如果第 n 位是 1,就表示从对象起始地址偏移 n * 4 字节的位置上存的是一个引用。
  uint32_t reference_instance_offsets_;
  // 类生命周期状态: 主要部分用于存储类的 ClassStatus
  // 子类型检查位 (Subtype Check Bits): 另一部分位用于一个叫做 “Subtype Check” 的优化。
  // 通过一些预计算的位掩码,ART 可以非常快速地判断 
  // A.class.isInstance(b) 或 b instanceof A 这样的类型检查,
  // 其速度远快于遍历整个继承链。
  uint32_t status_;
  
  uint16_t copied_methods_offset_;
  uint16_t virtual_methods_offset_;
  
  
  // 一些可变大小的数据,则被直接地、连续地附加在这些固定字段的内存之后。
  // The following data exist in real class objects.
  
  
  // Embedded Vtable length, for class object that's instantiable, fixed size.
  // uint32_t vtable_length_;
  
  // 这个字段在代码中并没有体现,ImTableEntry没有这个数据结构
  // 我有理由怀疑这是24年提交“使用可变大小的引用偏移位图来加速 VisitReferences()”这个Commit的作者的
  // 笔误, 当时也许在开发了一部分的ImTableEntry,但是没有完成,在提交这个commit的时候混入了这段注释。
  // Embedded Imtable pointer, for class object that's not an interface, fixed size.
  // ImTableEntry embedded_imtable_;
  
  // vtable_ 字段本身只是一个指针,而真正的虚方法表数组内容就存放在这片嵌入式内存区里。
  // 这减少了一次额外的内存分配和指针解引用,提升了 invoke-virtual 的性能。
  // Embedded Vtable, for class object that's not an interface, variable size.
  // VTableEntry embedded_vtable_[0];
  
  // 所有静态字段 (Static Fields) 的实际值也存放在这里。
  // 当访问一个静态字段时,ART 通过 ArtField 中的偏移量,直接在这个嵌入式区域中定位并读写数据。
  // Static fields, variable size.
  // uint32_t fields_[0];
  
  
  // 如果一个类的实例引用字段太多,一个 32 位的 reference_instance_offsets_ 位图不够用,
  // 那么一个更大 的、可变长度的位图就会被存放在这里。
  // Embedded bitmap of offsets of instance fields, for classes that need more than 31
  // reference-offset bits. 'reference_instance_offsets_' stores the number of
  // 32-bit entries that hold the entire bitmap. We compute the offset of first
  // entry by subtracting this number from class_size_.
  // uint32_t reference_bitmap_[0];
};