ClassLinker
ClassLinker 是 ART在 AOSP 中负责“Java 类”加载与链接的核心组件。它的主要职责包括:
- 加载解析 DEX 文件
- 在运行时解析并绑定类、方法符号引用
- 维护类表结构
- 在需要时初始化与准备类
初始化
在运行时创建的时候ClassLinker被创建,Runtime::Init
函数中
flowchart TD
A[In Runtime Initialization] --> B{IsAotCompiler?}
B -->|Yes| C[Create AotClassLinker via compiler_callbacks]
B -->|No| D[Create Standard ClassLinker]
C --> E{HasBootImageSpace?}
D --> E
E -->|Yes| F[Boot Image]
E -->|No| G[No Boot Image]
F --> H[class_linker_->InitFromBootImage]
H --> I[Add Image Strings to InternTable]
I --> J{Boot Image Components < Boot Class Path Size?}
J -->|Yes| K[Open Extra Boot DEX Files]
J -->|No| L[Complete - Boot Image Ready]
K --> M[class_linker_->AddExtraBootDexFiles]
M --> L
G --> N[OpenBootDexFiles - All Boot Class Path]
N --> O[class_linker_->InitWithoutImage]
O --> P[SetInstructionSet]
P --> Q[Create CalleeSave Methods]
Q --> R[Complete - class_linker Ready]
L --> S[class_linker Initialized with Boot Image]
R --> T[class_linker Initialized without Boot Image]
打开加载Dex文件
在ClassLinker的初始化的同时,还完成了打开、解析所有 bootclasspath 的 DEX 文件的步骤
OpenBootDexFiles
负责批量打开并加载BCP的 DEX 文件,将它们封装为 DexFile
对象并收集到输出列表中,统计打开失败的文件数量。
ArtDexFileLoader dex_file_loader(dex_filename, file, dex_location);
if (!dex_file_loader.Open(verify, kVerifyChecksum, &error_msg, out_dex_files)) {
LOG(WARNING) << "Failed to open .dex …";
++failure_count;
}
通过 ArtDexFileLoader
尝试打开DEX。
成功时,会把对应的 unique_ptr<DexFile>
加入 out_dex_files
;失败时记录错误并计数。
ArtDexFileLoader会根据对应的Dex文件构造出对应的DexFile的对象
InitFromBootImage
存在2个初始化函数,分别在Runtime::Init的时候根据bootimage的有无进行初始化
// Initialize class linker by bootstraping from dex files.
bool InitWithoutImage(std::vector<std::unique_ptr<const DexFile>> boot_class_path,
std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_);
// Initialize class linker from one or more boot images.
bool InitFromBootImage(std::string* error_msg)
REQUIRES_SHARED(Locks::mutator_lock_)
REQUIRES(!Locks::dex_lock_);
对于存在Bootimage的时候,InitFromBootImage执行流程如下
flowchart TD
A[InitFromBootImage] --> B[获取 BootImageSpaces]
B --> C[验证 ImageHeader 和 PointerSize]
C --> D{PointerSize 有效?}
D -->|No| E[返回错误]
D -->|Yes| F[从Image中读取,初始化一些关键的内置方法: <br/> Runtime三个ArtMethod指针成员 resolution_method_ 、 imt_conflict_method_ 、 imt_unimplemented_method_ <br/> 这些是 ART 用于处理动态链接、处理接口方法表(IMT)冲突、接口方法未实现的高度优化的小段代码。]
F --> G[从Image中读取,使用 SetCalleeSaveMethod 初始化6个CalleeSave方法,保存在Runtime指针数组类型的callee_save_methods_中。因为ART 并不总是需要保存所有 Callee-Save 寄存器。为了极致的性能,它准备了多套方案,包括:<br/> kSaveAllCalleeSavesMethod、 kSaveRefsOnlyMethod、 kSaveRefsAndArgsMethod、 kSaveEverythingMethod、 kSaveEverythingMethodForClinit、 kSaveEverythingMethodForSuspendCheck]
G --> H[注册每个ImageSpace对应的OatFiles到oat_file_manager中]
H --> I[从第一个 OAT 文件中,加载并设置一系列关键的Trampoline函数指针。]
I --> J[Trampoline函数包括:<br/> jni_dlsym_lookup_trampoline_<br/> quick_resolution_trampoline_<br/> quick_imt_conflict_trampoline_<br/> quick_generic_jni_trampoline_ <br/> quick_to_interpreter_bridge_trampoline_<br/> nterp_trampoline_]
J --> K{Debug 模式?}
K -->|Yes| L[验证所有 OatFile 的 Trampoline 一致性]
K -->|No| M[从image中读取Java核心的一些类对象到一个类对象数组的GCRoot中class_roots_]
L --> N{Trampoline 一致?}
N -->|No| O[检查 ArtMethod 入口点]
N -->|Yes| M
O --> P{发现错误入口点?}
P -->|Yes| Q[返回错误]
P -->|No| M
M --> R[从 ImageHeader 获取 boot_image_live_objects,这些是必须在运行时保持存活的关键对象]
R --> S[从boot_image_live_objects中拿到Sentinel 对象,设置Runtime的成员sentinel_,用于处理 JNI(例如,已被清除的弱引用)和 JDWP中的各种无效引用情况。]
S --> T[AddImageSpaces<br/>1. 找到镜像中包含的所有 DexFile 对象,添加到ClassLinker 的 boot_dex_files_ <br/> 2.根据当前运行时对镜像内容进行一些调整,比如设置ArtMethod的各类 trampoline,设置hotness_threshold <br/> 3. 将所有类批量添加到 ClassLoader 的 class_table(Boot class path table的是ClassLinker的boot_class_table_),使其可被查找和使用]
T --> U{非调试模式?}
U -->|Yes| V[缓存 JNI Stub 方法到boot_image_jni_stubs_,这是针对不同参数的JNI调用生成的专用的trampoline函数]
U -->|No| W[InitializeObjectVirtualMethodHashes 把当前这个ClassLinker的每一个VirtualMethods的hash值预先计算出来]
V --> W
W --> X[FinishInit<br/>1. 绑定 String 的初始化方法, String 是一个非常特殊的类, ART 内部有Ljava/lang/StringFactory;专门处理各种String的初始化工作 <br/> 2. 确保所有class_roots_类都已成功加载并正确初始化 <br/> 3. 设置 init_done_ 标志 <br/> 4.预初始化 StackOverflowError]
X --> Y[初始化完成]
style A fill:#e1f5fe
style E fill:#ffebee
style Q fill:#ffebee
style Y fill:#e8f5e8
style F fill:#fff3e0
style I fill:#f3e5f5
style M fill:#e0f2f1
各种Trampoline函数解释
- jni_dlsym_lookup_trampoline_:用于处理动态注册的 JNI 方法。当 Java 代码调用一个通过 C++ RegisterNatives 函数绑定的 native 方法时,会走到这个跳板来进行符号查找
- jni_dlsym_lookup_critical_trampoline_ 同上,只用于标记为 @CriticalNative 的 JNI 方法(不使用Java对象)
- quick_resolution_trampoline_: 当一个方法第一次被调用时,ART 需要找到它在内存中的真正地址,然后把调用点的指令修正为直接指向目标地址,这样下一次调用就快了
- quick_imt_conflict_trampoline_: 如果一个类实现了多个接口,且这些接口中有签名相同的方法,就会产生“冲突”。调用这种冲突方法时,就会先跳转到这个跳板,由它来执行更复杂的逻辑,以确定到底该调用哪个实现。
- quick_generic_jni_trampoline_: 通用 JNI 跳板。这是最标准、最常用的 JNI 调用入口。
- quick_to_interpreter_bridge_trampoline_: AOT 编译的代码需要调用一个没有被编译的方法时,执行流就会跳转到这个跳板。它负责将当前的执行状态从“原生模式”转为“解释模式”,然后由解释器来执行目标方法。
- nterp_trampoline_ : 这个跳板是所有需要由 Nterp 执行的方法的入口点。
boot_image_live_objects有哪些?
BootImageLiveObjects枚举中定义
-
kOomeWhenThrowingException: 是一个OutOfMemoryError 对象。当虚拟机在抛出任何普通异常的过程中,突然耗尽了内存时,使用这个预分配的 OutOfMemoryError。
-
kOomeWhenThrowingOome: OutOfMemoryError 对象。 这是更极端的情况。当虚拟机在抛出 OutOfMemoryError 的过程中,又一次耗尽内存时,使用这个实例。
-
kOomeWhenHandlingStackOverflow: 当发生栈溢出 StackOverflowError 时,处理这个错误本身也可能需要少量堆内存。如果此时堆内存也刚好用完,就使用这个预分配的 OutOfMemoryError 来报告这个复合型灾难。
-
kNoClassDefFoundError: NoClassDefFoundError 对象。 找不到类定义。
-
kClearedJniWeakSentinel: 一个普通的 Object。用于处理 JNI(例如,已被清除的弱引用)和 JDWP(Java 调试线协议,例如,无效的引用)中的各种无效情况。
-
kIntrinsicObjectsStart: 这不是一个对象,而是一个标记。它标志着在这个索引之后,数组里存放的是用于”Intrinsics”(内建函数)的预分配对象。它起到了一个分割线的作用。
Sentinel怎么起作用的?
SweepJniWeakGlobals 函数专门负责清理 JNI 弱全局引用
void IndirectReferenceTable::SweepJniWeakGlobals(IsMarkedVisitor* visitor) {
CHECK_EQ(kind_, kWeakGlobal);
MutexLock mu(Thread::Current(), *Locks::jni_weak_globals_lock_);
Runtime* const runtime = Runtime::Current();
for (size_t i = 0, capacity = Capacity(); i != capacity; ++i) {
GcRoot<mirror::Object>* entry = table_[i].GetReference();
// Need to skip null here to distinguish between null entries and cleared weak ref entries.
if (!entry->IsNull()) {
mirror::Object* obj = entry->Read<kWithoutReadBarrier>();
// 对于每个有效的引用,它会通过 visitor->IsMarked(obj) 来询问垃圾回收器:“这个引用指向的 Java 对象在刚才的 GC 过程中是否还存活?”
mirror::Object* new_obj = visitor->IsMarked(obj);
// 如果对象存活: IsMarked 会返回这个对象的新地址(GC 可能是移动式的,对象地址会变)。代码会用新地址更新引用表。
// 如果对象已被回收: IsMarked 会返回 nullptr。这表明这个弱引用现在指向了一个不存在的对象,它变成了一个“悬空指针”。
if (new_obj == nullptr) {
// 在发现对象被回收后 (new_obj == nullptr) 用这个哨兵对象来覆盖引用表中原来的条目。
new_obj = runtime->GetClearedJniWeakGlobal();
}
*entry = GcRoot<mirror::Object>(new_obj);
}
}
}
一个引用表条目如果是 nullptr,通常意味着这个位置是空的、未被使用。
而一个条目如果是哨兵对象,则明确地表示“这里曾经有一个弱引用,但它指向的对象已经被 GC 回收了”。 这样就清楚地区分了“未使用”和“已被清除”这两种完全不同的状态。
通过使用哨兵对象,ART 的实现变得简单:所有被清除的弱引用都指向同一个哨-兵对象。因此,IsSameObject 函数的内部逻辑只需要判断 weak_ref 是不是指向哨兵对象即可,而不需要为每个弱引用维护一个单独的“是否已清除”的标志。
InitWithoutImage
graph TD
A[Start InitWithoutImage] --> B[禁止MovingGC,手动创建基础核心类 java_lang_Class、 java_lang_Class数组、 java_lang_Object及其数组、java_lang_String];
B --> C[设置 Sentinel对象 ];
C --> D[创建 Class Roots 并存储核心类,具体定义在 CLASS_ROOT_LIST宏中 ];
D --> E[创建所有原始类型Primitive及其数组的Class];
E --> F[创建 DexCache, dalvik.system.ClassExt 等基础设施类, 创建并设置resolution_method_ 、 imt_conflict_method_ 、 imt_unimplemented_method_];
F --> G[加载并注册 Boot Class Path 中的所有 Dex 文件到 boot_dex_files_];
G --> H[FindSystemClass 可用];
H --> HA[设置Trampoline函数];
HA --> I[重新check并完善(计算hash值)核心类];
I --> J[设置 Cloneable/Serializable];
J --> K[加载并设置其余所有关键系统类: 反射/引用/异常/ClassLoader等];
K --> L[调用 FinishInit 完成最后工作];
L --> M[End InitWithoutImage];