热修&插件 - Art加载Class流程
根据上篇文章了解,对应ClassLoader初始化时,会将对应的dex加载到内存。接下来再继续看Class的加载、链接和初始化过程。
一、整体过程
类加载、链接、初始化整体过程任务 | 描述 | 最终状态 |
---|---|---|
Load | 从.odex/.dex中提取类信息到虚拟机内部。 | kStatusLoaded |
Verify | 校验类信息是否合法。 | kStatusVerified |
Prepare | 为该类和相关信息的存储分配一块存储空间。 | |
Resolve | 如果该类的成员还有引用其他类,可能还需要将其他类加载到虚拟机。 | kStatusResolved |
Initialize | 初始化这个类(初始化静态成员变量、执行static语句块)。 | kStatusInitialized |
虽然如图所示,Class的加载、链接、初始化分别对应Load、Link、Initialize三个阶段,但ART代码中相关函数的调用顺序却不能(也没必要)完全与上述三个阶段相对应。
注:class.h中的完整的类状态
enum Status {
kStatusRetired = -3, // Retired, should not be used. Use the newly cloned one instead.
kStatusErrorResolved = -2,
kStatusErrorUnresolved = -1,
kStatusNotReady = 0,
kStatusIdx = 1, // Loaded, DEX idx in super_class_type_idx_ and interfaces_type_idx_.
kStatusLoaded = 2, // DEX idx values resolved.
kStatusResolving = 3, // Just cloned from temporary class object.
kStatusResolved = 4, // Part of linking.
kStatusVerifying = 5, // In the process of being verified.
kStatusRetryVerificationAtRuntime = 6, // Compile time verification failed, retry at runtime.
kStatusVerifyingAtRuntime = 7, // Retrying verification at runtime.
kStatusVerified = 8, // Logically part of linking; done pre-init.
kStatusInitializing = 9, // Class init in progress.
kStatusInitialized = 10, // Ready to go.
kStatusMax = 11,
};
二、从ClassLoader.loadClass()看类加载过程
2.1ClassLoader继承关系
ClassLoader继承关系类图2.2 ClassLoader.loadClass()加载流程
libcore/ojluni/src/main/java/java/lang/ClassLoader.java
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先,判断当前类是否已经被加载过
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
if (parent != null) {
//其次,递归去父类中查询是否加载到缓存
c = parent.loadClass(name, false);
} else {
//均未缓存,去BootClassLoader中查找
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
//以上均未找到,则通过findClass去找
c = findClass(name);
}
}
return c;
}
以上即:双亲委派机制。目的是:避免重复加载以及核心累篡改。
接下来看findClass流程:
findClass调用栈这里核心在于9。
art/runtime/class_linker.cc
mirror::Class* ClassLinker::DefineClass(Thread* self,
const char* descriptor,//目标类的字符串描述
size_t hash,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,//该类所在的dex文件对象
const DexFile::ClassDef& dex_class_def//描述要加载的类在dex文件里的信息
) {
StackHandleScope<3> hs(self);
auto klass = hs.NewHandle<mirror::Class>(nullptr);
...
if (klass == nullptr) {
//分配一个class对象
klass.Assign(AllocClass(self, SizeOfClassWithoutEmbeddedTables(dex_file, dex_class_def)));
}
…
//获取DexCache
ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get());
if (dex_cache == nullptr) {
self->AssertPendingException();
return nullptr;
}
klass->SetDexCache(dex_cache);
//kclass设置基本信息:
//包括:AccessFlags、Status、DexClassDefIndex、DexTypeIndex等。
SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get());
...
// Add the newly loaded class to the loaded classes table.
//将kclass加入到class_table中
ObjPtr<mirror::Class> existing = InsertClass(descriptor, klass.Get(), hash);
if (existing != nullptr) {
// We failed to insert because we raced with another thread. Calling EnsureResolved may cause
// this thread to block.
//如果插入失败,则表示类已经被加载过
return EnsureResolved(self, descriptor, existing);
}
// Load the fields and other things after we are inserted in the table. This is so that we don't
// end up allocating unfree-able linear alloc resources and then lose the race condition. The
// other reason is that the field roots are only visited from the class table. So we need to be
// inserted before we allocate / fill in these fields.
//加载来自dex中目标类的属性和其他信息
LoadClass(self, *new_dex_file, *new_class_def, klass);
…
if (!LoadSuperAndInterfaces(klass, *new_dex_file)) {
// Loading failed.
if (!klass->IsErroneous()) {
mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
}
return nullptr;
}
...
MutableHandle<mirror::Class> h_new_class = hs.NewHandle<mirror::Class>(nullptr);
if (!LinkClass(self, descriptor, klass, interfaces, &h_new_class)) {
// Linking failed.
if (!klass->IsErroneous()) {
mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
}
return nullptr;
}
...
return h_new_class.Get();
}
初始化一个mirror Class对象:kclass,优先从DexCache中获取对应的类信息,通过SetupClass设置基本信息,将kclass加入到class_table中,再通过LoadClass和LoadSuperAndInterfaces分别加载dex中目标类的相关信息以及其父类或者实现的接口的相关信息。
先看LoadClass
void ClassLinker::LoadClass(Thread* self,
const DexFile& dex_file,
const DexFile::ClassDef& dex_class_def,
Handle<mirror::Class> klass) {
//获取classData
const uint8_t* class_data = dex_file.GetClassData(dex_class_def);
if (class_data == nullptr) {
return; // no fields or methods - for example a marker interface
}
//加载类的属性和方法
LoadClassMembers(self, dex_file, class_data, klass);
}
void ClassLinker::LoadClassMembers(Thread* self,
const DexFile& dex_file,
const uint8_t* class_data,
Handle<mirror::Class> klass) {
{
// Note: We cannot have thread suspension until the field and method arrays are setup or else
// Class::VisitFieldRoots may miss some fields or methods.
//在没有加载完类成员之前,不允许当前线程挂起
ScopedAssertNoThreadSuspension nts(__FUNCTION__);
// Load static fields.
// We allow duplicate definitions of the same field in a class_data_item
// but ignore the repeated indexes here, b/21868015.
//加载静态属性
LinearAlloc* const allocator = GetAllocatorForClassLoader(klass->GetClassLoader());
ClassDataItemIterator it(dex_file, class_data);
LengthPrefixedArray<ArtField>* sfields = AllocArtFieldArray(self,
allocator,
it.NumStaticFields());
size_t num_sfields = 0;
uint32_t last_field_idx = 0u;
for (; it.HasNextStaticField(); it.Next()) {
uint32_t field_idx = it.GetMemberIndex();
DCHECK_GE(field_idx, last_field_idx); // Ordering enforced by DexFileVerifier.
if (num_sfields == 0 || LIKELY(field_idx > last_field_idx)) {
DCHECK_LT(num_sfields, it.NumStaticFields());
//加载属性
LoadField(it, klass, &sfields->At(num_sfields));
++num_sfields;
last_field_idx = field_idx;
}
}
// Load instance fields.
//加载实例属性
LengthPrefixedArray<ArtField>* ifields = AllocArtFieldArray(self,
allocator,
it.NumInstanceFields());
size_t num_ifields = 0u;
last_field_idx = 0u;
for (; it.HasNextInstanceField(); it.Next()) {
uint32_t field_idx = it.GetMemberIndex();
DCHECK_GE(field_idx, last_field_idx); // Ordering enforced by DexFileVerifier.
if (num_ifields == 0 || LIKELY(field_idx > last_field_idx)) {
DCHECK_LT(num_ifields, it.NumInstanceFields());
LoadField(it, klass, &ifields->At(num_ifields));
++num_ifields;
last_field_idx = field_idx;
}
}
...
// Set the field arrays.
klass->SetSFieldsPtr(sfields);
DCHECK_EQ(klass->NumStaticFields(), num_sfields);
klass->SetIFieldsPtr(ifields);
DCHECK_EQ(klass->NumInstanceFields(), num_ifields);
// Load methods.
bool has_oat_class = false;
//获取OatClass,通过OatClass可以获取类方法的本地机器指令
const OatFile::OatClass oat_class =
(Runtime::Current()->IsStarted() && !Runtime::Current()->IsAotCompiler())
? OatFile::FindOatClass(dex_file, klass->GetDexClassDefIndex(), &has_oat_class)
: OatFile::OatClass::Invalid();
const OatFile::OatClass* oat_class_ptr = has_oat_class ? &oat_class : nullptr;
//加载方法
klass->SetMethodsPtr(
AllocArtMethodArray(self, allocator, it.NumDirectMethods() + it.NumVirtualMethods()),
it.NumDirectMethods(),
it.NumVirtualMethods());
size_t class_def_method_index = 0;
uint32_t last_dex_method_index = DexFile::kDexNoIndex;
size_t last_class_def_method_index = 0;
// TODO These should really use the iterators.
//加载直接方法
for (size_t i = 0; it.HasNextDirectMethod(); i++, it.Next()) {
ArtMethod* method = klass->GetDirectMethodUnchecked(i, image_pointer_size_);
LoadMethod(dex_file, it, klass, method);
LinkCode(this, method, oat_class_ptr, class_def_method_index);
uint32_t it_method_index = it.GetMemberIndex();
if (last_dex_method_index == it_method_index) {
// duplicate case
method->SetMethodIndex(last_class_def_method_index);
} else {
method->SetMethodIndex(class_def_method_index);
last_dex_method_index = it_method_index;
last_class_def_method_index = class_def_method_index;
}
class_def_method_index++;
}
//加载虚拟方法
for (size_t i = 0; it.HasNextVirtualMethod(); i++, it.Next()) {
ArtMethod* method = klass->GetVirtualMethodUnchecked(i, image_pointer_size_);
LoadMethod(dex_file, it, klass, method);
DCHECK_EQ(class_def_method_index, it.NumDirectMethods() + i);
//将对应方法关联上指令
LinkCode(this, method, oat_class_ptr, class_def_method_index);
class_def_method_index++;
}
DCHECK(!it.HasNext());
}
// Ensure that the card is marked so that remembered sets pick up native roots.
Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(klass.Get());
self->AllowThreadSuspension();
}
加载类的属性和方法:
ArtField
: 包括Static Field、Instance Field、Field
ArtMethod
: 包括Direct Method、Virtual Method。
注:ArtField和ArtMethod在ART中分别用于描述一个类的成员变量和成员函数。
class.h
uint64_t ifields_ 和 uint64_t sfields_ 对应ArtField
uint64_t methods_; 对应ArtMethod
LinkCode 将对应方法关联上指令
static void LinkCode(ClassLinker* class_linker,
ArtMethod* method,
const OatFile::OatClass* oat_class,
uint32_t class_def_method_index) REQUIRES_SHARED(Locks::mutator_lock_) {
Runtime* const runtime = Runtime::Current();
if (runtime->IsAotCompiler()) {
// The following code only applies to a non-compiler runtime.
return;
}
// Method shouldn't have already been linked.
DCHECK(method->GetEntryPointFromQuickCompiledCode() == nullptr);
if (oat_class != nullptr) {
// Every kind of method should at least get an invoke stub from the oat_method.
// non-abstract methods also get their code pointers.
//从OatClass中获取对应的OatMethod,OatMethod中记录了方法的本地机器指令的偏移地址
//通过OatMethod::LinkMethod将OatMethod中记录的信息设置到ArtMethod当中
const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index);
oat_method.LinkMethod(method);
}
const void* quick_code = method->GetEntryPointFromQuickCompiledCode();
bool enter_interpreter = class_linker->ShouldUseInterpreterEntrypoint(method, quick_code);
if (!method->IsInvokable()) {
EnsureThrowsInvocationError(class_linker, method);
return;
}
if (method->IsStatic() && !method->IsConstructor()) {//静态非构造方法
// For static methods excluding the class initializer, install the trampoline.
// It will be replaced by the proper entry point by ClassLinker::FixupStaticTrampolines
// after initializing class (see ClassLinker::InitializeClass method).
method->SetEntryPointFromQuickCompiledCode(GetQuickResolutionStub());
} else if (quick_code == nullptr && method->IsNative()) {//native方法且没有本地机器码
method->SetEntryPointFromQuickCompiledCode(GetQuickGenericJniStub());
} else if (enter_interpreter) {
// Set entry point from compiled code if there's no code or in interpreter only mode.
method->SetEntryPointFromQuickCompiledCode(GetQuickToInterpreterBridge());
}
if (method->IsNative()) {
// Unregistering restores the dlsym lookup stub.
method->UnregisterNative();
if (enter_interpreter || quick_code == nullptr) {
// We have a native method here without code. Then it should have either the generic JNI
// trampoline as entrypoint (non-static), or the resolution trampoline (static).
// TODO: this doesn't handle all the cases where trampolines may be installed.
const void* entry_point = method->GetEntryPointFromQuickCompiledCode();
DCHECK(class_linker->IsQuickGenericJniStub(entry_point) ||
class_linker->IsQuickResolutionStub(entry_point));
}
}
}
这里通过method->SetEntryPointFromQuickCompiledCode来设置对应方法的入口函数:
- 对于静态非构造方法,当有执行机器指令的方法调用这类函数时,入口函数是:GetQuickResolutionStub()。
- 当一个执行本地机器指令的方法调用没有机器指令的native方法时,入口函数是GetQuickGenericJniStub()。
- 抽象方法需要由被子类实现,所以抽象类是没有本地机器指令的,需要由解释器解释执行,当有机器指令的方法调用抽象方法时, 需要设置一个桥接函数,从而转到解释器,由解释器解释执行抽象方法, 这个桥接函数是GetQuickToInterpreterBridge()。
为什么每个类方法需要一个解释器入口点?
ART虚拟机执行Java方法主要有两种模式:
-
quick code
:执行本地机器指令 -
interpreter
:由解释器解释执行虚拟机字节码
当以解释器执行的类方法在执行的过程中调用了其它的类方法时,解释器就需要进一步知道被调用的类方法是应用以解释方式执行,还是本地机器指令方法执行。为了能够进行统一处理,就给每一个类方法都设置一个解释器入口点。
例如:
抽象方法声明类中是没有实现的,必须要由子类实现。因此抽象方法在声明类中是没有对应的本地机器指令的,它们必须要通过解释器来执行。
然后返回来看DefineClass最后一步:LinkClass
bool ClassLinker::LinkClass(Thread* self,
const char* descriptor,
Handle<mirror::Class> klass,
Handle<mirror::ObjectArray<mirror::Class>> interfaces,
MutableHandle<mirror::Class>* h_new_class_out) {
CHECK_EQ(mirror::Class::kStatusLoaded, klass->GetStatus());
if (!LinkSuperClass(klass)) {//link超类
return false;
}
ArtMethod* imt_data[ImTable::kSize];
// If there are any new conflicts compared to super class.
bool new_conflict = false;
std::fill_n(imt_data, arraysize(imt_data), Runtime::Current()->GetImtUnimplementedMethod());
if (!LinkMethods(self, klass, interfaces, &new_conflict, imt_data)) {//link方法
return false;
}
if (!LinkInstanceFields(self, klass)) {//link对象属性
return false;
}
size_t class_size;
if (!LinkStaticFields(self, klass, &class_size)) {//link静态属性
return false;
}
CreateReferenceInstanceOffsets(klass);
CHECK_EQ(mirror::Class::kStatusLoaded, klass->GetStatus());
ImTable* imt = nullptr;
if (klass->ShouldHaveImt()) {
// If there are any new conflicts compared to the super class we can not make a copy. There
// can be cases where both will have a conflict method at the same slot without having the same
// set of conflicts. In this case, we can not share the IMT since the conflict table slow path
// will possibly create a table that is incorrect for either of the classes.
// Same IMT with new_conflict does not happen very often.
if (!new_conflict) {
ImTable* super_imt = FindSuperImt(klass.Get(), image_pointer_size_);
if (super_imt != nullptr) {
bool imt_equals = true;
for (size_t i = 0; i < ImTable::kSize && imt_equals; ++i) {
imt_equals = imt_equals && (super_imt->Get(i, image_pointer_size_) == imt_data[i]);
}
if (imt_equals) {
imt = super_imt;
}
}
}
if (imt == nullptr) {
LinearAlloc* allocator = GetAllocatorForClassLoader(klass->GetClassLoader());
imt = reinterpret_cast<ImTable*>(
allocator->Alloc(self, ImTable::SizeInBytes(image_pointer_size_)));
if (imt == nullptr) {
return false;
}
imt->Populate(imt_data, image_pointer_size_);
}
}
if (!klass->IsTemp() || (!init_done_ && klass->GetClassSize() == class_size)) {
// We don't need to retire this class as it has no embedded tables or it was created the
// correct size during class linker initialization.
CHECK_EQ(klass->GetClassSize(), class_size) << klass->PrettyDescriptor();
if (klass->ShouldHaveEmbeddedVTable()) {
klass->PopulateEmbeddedVTable(image_pointer_size_);
}
if (klass->ShouldHaveImt()) {
klass->SetImt(imt, image_pointer_size_);
}
// Update CHA info based on whether we override methods.
// Have to do this before setting the class as resolved which allows
// instantiation of klass.
Runtime::Current()->GetClassHierarchyAnalysis()->UpdateAfterLoadingOf(klass);
// This will notify waiters on klass that saw the not yet resolved
// class in the class_table_ during EnsureResolved.
//linkClass 输入状态为kStatusLoaded,输出状态为kStatusResolved
mirror::Class::SetStatus(klass, mirror::Class::kStatusResolved, self);
h_new_class_out->Assign(klass.Get());
} else {
CHECK(!klass->IsResolved());
// Retire the temporary class and create the correctly sized resolved class.
StackHandleScope<1> hs(self);
auto h_new_class = hs.NewHandle(klass->CopyOf(self, class_size, imt, image_pointer_size_));
// Set arrays to null since we don't want to have multiple classes with the same ArtField or
// ArtMethod array pointers. If this occurs, it causes bugs in remembered sets since the GC
// may not see any references to the target space and clean the card for a class if another
// class had the same array pointer.
klass->SetMethodsPtrUnchecked(nullptr, 0, 0);
klass->SetSFieldsPtrUnchecked(nullptr);
klass->SetIFieldsPtrUnchecked(nullptr);
if (UNLIKELY(h_new_class == nullptr)) {
self->AssertPendingOOMException();
mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self);
return false;
}
CHECK_EQ(h_new_class->GetClassSize(), class_size);
ObjectLock<mirror::Class> lock(self, h_new_class);
FixupTemporaryDeclaringClass(klass.Get(), h_new_class.Get());
{
WriterMutexLock mu(self, *Locks::classlinker_classes_lock_);
ObjPtr<mirror::ClassLoader> const class_loader = h_new_class.Get()->GetClassLoader();
ClassTable* const table = InsertClassTableForClassLoader(class_loader);
ObjPtr<mirror::Class> existing = table->UpdateClass(descriptor, h_new_class.Get(),
ComputeModifiedUtf8Hash(descriptor));
if (class_loader != nullptr) {
// We updated the class in the class table, perform the write barrier so that the GC knows
// about the change.
Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader);
}
CHECK_EQ(existing, klass.Get());
if (log_new_roots_) {
new_class_roots_.push_back(GcRoot<mirror::Class>(h_new_class.Get()));
}
}
// Update CHA info based on whether we override methods.
// Have to do this before setting the class as resolved which allows
// instantiation of klass.
Runtime::Current()->GetClassHierarchyAnalysis()->UpdateAfterLoadingOf(h_new_class);
// This will notify waiters on temp class that saw the not yet resolved class in the
// class_table_ during EnsureResolved.
mirror::Class::SetStatus(klass, mirror::Class::kStatusRetired, self);
CHECK_EQ(h_new_class->GetStatus(), mirror::Class::kStatusResolving);
// This will notify waiters on new_class that saw the not yet resolved
// class in the class_table_ during EnsureResolved.
mirror::Class::SetStatus(h_new_class, mirror::Class::kStatusResolved, self);
// Return the new class.
h_new_class_out->Assign(h_new_class.Get());
}
return true;
}
LinkClass整体过程是Prepare + Resolve,为该类和相关信息的存储分配一块存储空间,如果该类的成员还有引用其他类,可能还需要将其他类加载到虚拟机。输入状态为kStatusLoaded,输出状态为kStatusResolved。
这里我们发现,link过程并没有做Verify,那么Verify是在什么时候做的呢?
这里简单梳理下《深入理解Android Java虚拟机Art》中的结论:
类的Verify可以在dex2oat阶段进行,这叫预校验。如果出现错误,状态则置为:kStatusRetryVerificationAtRutime,如果是这个状态,此类在之后的虚拟机运行时还会再继续做校验。,如果dex2oat校验成功,状态则置为:kStatusVerified。该类在虚拟机运行时,如果类的初始状态为kStatusVerified,则将该类中methods_数组中所有的ArtMethod对象设置kAccSkipAccessChecks标志,同时还设置kAccVerificationAttempted标记(该标记表示该类已经校验过了,不需要再做校验)。如果类的初始状态为kStatusRetryVerificationAtRutime,则在虚拟机运行时会触发MethodVerifier对该类进行校验。
回顾下ClassLoader.loadClass()整个过程:
先通过双亲委派机制判断当前类是否被加载过、是否需要上层的父加载器去加载。如果都没有则走findClass流程,而findClass核心是ClassLinker::DefineClass:
该方法将类的信息从Dex文件中转换为对应的mirror Class对象。构建过程包括基本参数设置,属性、方法加载,其中方法还会设置对应的入口函数,通过linkClass完成准备和解析流程。最终加入到class_table中(它被ClassLoader用于管理该ClassLoader所加载的类)。这个过程虽然返回了Class对象,但是目前还只完成了加载操作,离目标类最终可用还差:验证工作和初始化相关工作。
那么findClass的数据结构转换过程总结如下:
类加载过程数据结构转换
数据获取流程:
class_table有表白已经被加载过,直接从loadClass流程就获取了,否则走DefineClass,优先从DexCache中获取dex信息,否则从当前DexFile中获取。然后将dex中的信息转换为对应的mirror Class对象,最后将class加入到class_table中,缓存当前类已经被指定的classLoader加载了。
参考:
《深入理解Android Java虚拟机Art》
https://www.jianshu.com/p/86f9db3ab430
https://www.jianshu.com/go-wild?ac=2&url=http%3A%2F%2Fblog.csdn.net%2Fluoshengyang%2Farticle%2Fdetails%2F39533503