背景
由于前前前阵子写了个壳,得去了解类的加载流程,当时记了一些潦草的笔记。这几天把这些东西简单梳理了一下,本文分析的代码基于Android8.1.0源码。
流程分析
从loadClass开始,我们来看下Android中类加载的流程
/libcore/ojluni/src/main/java/java/lang/ClassLoader.java::loadClass
loadClass流程如下:
protected Class<?> loadClass(String name, boolean resolve) | |
throws ClassNotFoundException | |
{ | |
// First, check if the class has already been loaded | |
Class<?> c = findLoadedClass(name); | |
if (c == null) { | |
try { | |
if (parent != null) { | |
c = parent.loadClass(name, false); | |
} else { | |
c = findBootstrapClassOrNull(name); | |
} | |
} catch (ClassNotFoundException e) { | |
// ClassNotFoundException thrown if class not found | |
// from the non-null parent class loader | |
} | |
if (c == null) { | |
// If still not found, then invoke findClass in order | |
// to find the class. | |
c = findClass(name); | |
} | |
} | |
return c; | |
} |
/libcore/ojluni/src/main/java/java/lang/ClassLoader.java::findClass
protected Class<?> findClass(String name) throws ClassNotFoundException { | |
throw new ClassNotFoundException(name); | |
} |
ClassLoader类的findClass是没有实际查找代码的,所以调用findClass其实是调用其实现类的findClass函数,例如:BaseDexClassLoader
/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java::findClass
每个BaseDexClassLoader都持有一个DexPathList,BaseDexClassLoader的findClass类调用了DexPathList的findClass。
protected Class<?> findClass(String name) throws ClassNotFoundException { | |
List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); | |
Class c = pathList.findClass(name, suppressedExceptions); | |
if (c == null) { | |
ClassNotFoundException cnfe = new ClassNotFoundException( | |
"Didn't find class \"" + name + "\" on path: " + pathList); | |
for (Throwable t : suppressedExceptions) { | |
cnfe.addSuppressed(t); | |
} | |
throw cnfe; | |
} | |
return c; | |
} |
/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java::findClass
遍历所有dexElements,并调用Element类的findClass。
public Class<?> findClass(String name, List<Throwable> suppressed) { | |
for (Element element : dexElements) { | |
Class<?> clazz = element.findClass(name, definingContext, suppressed); | |
if (clazz != null) { | |
return clazz; | |
} | |
} | |
if (dexElementsSuppressedExceptions != null) { | |
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); | |
} | |
return null; | |
} |
题外话,dexElements对象其实是DexPathList$Element类的数组,用于存储已加载的dex或者jar的信息。
/libcore/dalvik/src/main/java/dalvik/system/DexPathList$Element::findClass
Element的findClass,又去调用DexFile类的loadClassBinaryName,可以理解为在单独的dex或者jar对象中加载类
public Class<?> findClass(String name, ClassLoader definingContext, | |
List<Throwable> suppressed) { | |
return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) | |
: null; | |
} |
libcore\dalvik\src\main\java\dalvik\system\DexFile.java::loadClassBinaryName
去调用defineClass函数
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { | |
return defineClass(name, loader, mCookie, this, suppressed); | |
} |
libcore\dalvik\src\main\java\dalvik\system\DexFile.java::defineClass
调用defineClassNative,准备进入Native层
private static Class defineClass(String name, ClassLoader loader, Object cookie, | |
DexFile dexFile, List<Throwable> suppressed) { | |
Class result = null; | |
try { | |
result = defineClassNative(name, loader, cookie, dexFile); | |
} catch (NoClassDefFoundError e) { | |
if (suppressed != null) { | |
suppressed.add(e); | |
} | |
} catch (ClassNotFoundException e) { | |
if (suppressed != null) { | |
suppressed.add(e); | |
} | |
} | |
return result; | |
} |
art\runtime\native\dalvik_system_DexFile.cc::DexFile_defineClassNative
检查dex是否加载,类名是否合理,并遍历DexFile对象,查找Dex文件中的类的定义,找到就去调用ClassLinker::DefineClass函数。
static jclass DexFile_defineClassNative(JNIEnv* env, | |
jclass, | |
jstring javaName, | |
jobject javaLoader, | |
jobject cookie, | |
jobject dexFile) { | |
std::vector<const DexFile*> dex_files; | |
const OatFile* oat_file; | |
if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) { | |
VLOG(class_linker) << "Failed to find dex_file"; | |
DCHECK(env->ExceptionCheck()); | |
return nullptr; | |
} | |
ScopedUtfChars class_name(env, javaName); | |
if (class_name.c_str() == nullptr) { | |
VLOG(class_linker) << "Failed to find class_name"; | |
return nullptr; | |
} | |
const std::string descriptor(DotToDescriptor(class_name.c_str())); | |
const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); | |
for (auto& dex_file : dex_files) { | |
const DexFile::ClassDef* dex_class_def = | |
OatDexFile::FindClassDef(*dex_file, descriptor.c_str(), hash); | |
if (dex_class_def != nullptr) { | |
ScopedObjectAccess soa(env); | |
ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); | |
StackHandleScope<1> hs(soa.Self()); | |
Handle<mirror::ClassLoader> class_loader( | |
hs.NewHandle(soa.Decode<mirror::ClassLoader>(javaLoader))); | |
ObjPtr<mirror::DexCache> dex_cache = | |
class_linker->RegisterDexFile(*dex_file, class_loader.Get()); | |
if (dex_cache == nullptr) { | |
// OOME or InternalError (dexFile already registered with a different class loader). | |
soa.Self()->AssertPendingException(); | |
return nullptr; | |
} | |
ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(), | |
descriptor.c_str(), | |
hash, | |
class_loader, | |
*dex_file, | |
*dex_class_def); | |
// Add the used dex file. This only required for the DexFile.loadClass API since normal | |
// class loaders already keep their dex files live. | |
class_linker->InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile), | |
class_loader.Get()); | |
if (result != nullptr) { | |
VLOG(class_linker) << "DexFile_defineClassNative returning " << result | |
<< " for " << class_name.c_str(); | |
return soa.AddLocalReference<jclass>(result); | |
} | |
} | |
} | |
VLOG(class_linker) << "Failed to find dex_class_def " << class_name.c_str(); | |
return nullptr; | |
} |
art\runtime\class_linker.cc::DefineClass
DefineClass这个函数做了许多工作,相当于底层类加载逻辑的分发器,整体逻辑如下图:
mirror::Class* ClassLinker::DefineClass(Thread* self, | |
const char* descriptor, | |
size_t hash, | |
Handle<mirror::ClassLoader> class_loader, | |
const DexFile& dex_file, | |
const DexFile::ClassDef& dex_class_def) { | |
StackHandleScope<3> hs(self); | |
auto klass = hs.NewHandle<mirror::Class>(nullptr); | |
...... | |
// Get the real dex file. This will return the input if there aren't any callbacks or they do | |
// nothing. | |
DexFile const* new_dex_file = nullptr; | |
DexFile::ClassDef const* new_class_def = nullptr; | |
// TODO We should ideally figure out some way to move this after we get a lock on the klass so it | |
// will only be called once. | |
Runtime::Current()->GetRuntimeCallbacks()->ClassPreDefine(descriptor, | |
klass, | |
class_loader, | |
dex_file, | |
dex_class_def, | |
&new_dex_file, | |
&new_class_def); | |
// Check to see if an exception happened during runtime callbacks. Return if so. | |
if (self->IsExceptionPending()) { | |
return nullptr; | |
} | |
ObjPtr<mirror::DexCache> dex_cache = RegisterDexFile(*new_dex_file, class_loader.Get()); | |
if (dex_cache == nullptr) { | |
self->AssertPendingException(); | |
return nullptr; | |
} | |
klass->SetDexCache(dex_cache); | |
SetupClass(*new_dex_file, *new_class_def, klass, class_loader.Get()); | |
// Mark the string class by setting its access flag. | |
if (UNLIKELY(!init_done_)) { | |
if (strcmp(descriptor, "Ljava/lang/String;") == 0) { | |
klass->SetStringClass(); | |
} | |
} | |
ObjectLock<mirror::Class> lock(self, klass); | |
klass->SetClinitThreadId(self->GetTid()); | |
// Make sure we have a valid empty iftable even if there are errors. | |
klass->SetIfTable(GetClassRoot(kJavaLangObject)->GetIfTable()); | |
// Add the newly loaded class to the loaded classes 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. | |
LoadClass(self, *new_dex_file, *new_class_def, klass); | |
if (self->IsExceptionPending()) { | |
VLOG(class_linker) << self->GetException()->Dump(); | |
// An exception occured during load, set status to erroneous while holding klass' lock in case | |
// notification is necessary. | |
if (!klass->IsErroneous()) { | |
mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); | |
} | |
return nullptr; | |
} | |
// Finish loading (if necessary) by finding parents | |
CHECK(!klass->IsLoaded()); | |
if (!LoadSuperAndInterfaces(klass, *new_dex_file)) { | |
// Loading failed. | |
if (!klass->IsErroneous()) { | |
mirror::Class::SetStatus(klass, mirror::Class::kStatusErrorUnresolved, self); | |
} | |
return nullptr; | |
} | |
CHECK(klass->IsLoaded()); | |
// At this point the class is loaded. Publish a ClassLoad event. | |
// Note: this may be a temporary class. It is a listener's responsibility to handle this. | |
Runtime::Current()->GetRuntimeCallbacks()->ClassLoad(klass); | |
// Link the class (if necessary) | |
CHECK(!klass->IsResolved()); | |
// TODO: Use fast jobjects? | |
auto interfaces = hs.NewHandle<mirror::ObjectArray<mirror::Class>>(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; | |
} | |
self->AssertNoPendingException(); | |
CHECK(h_new_class != nullptr) << descriptor; | |
CHECK(h_new_class->IsResolved() && !h_new_class->IsErroneousResolved()) << descriptor; | |
// Instrumentation may have updated entrypoints for all methods of all | |
// classes. However it could not update methods of this class while we | |
// were loading it. Now the class is resolved, we can update entrypoints | |
// as required by instrumentation. | |
if (Runtime::Current()->GetInstrumentation()->AreExitStubsInstalled()) { | |
// We must be in the kRunnable state to prevent instrumentation from | |
// suspending all threads to update entrypoints while we are doing it | |
// for this class. | |
DCHECK_EQ(self->GetState(), kRunnable); | |
Runtime::Current()->GetInstrumentation()->InstallStubsForClass(h_new_class.Get()); | |
} | |
/* | |
* We send CLASS_PREPARE events to the debugger from here. The | |
* definition of "preparation" is creating the static fields for a | |
* class and initializing them to the standard default values, but not | |
* executing any code (that comes later, during "initialization"). | |
* | |
* We did the static preparation in LinkClass. | |
* | |
* The class has been prepared and resolved but possibly not yet verified | |
* at this point. | |
*/ | |
Runtime::Current()->GetRuntimeCallbacks()->ClassPrepare(klass, h_new_class); | |
// Notify native debugger of the new class and its layout. | |
jit::Jit::NewTypeLoadedIfUsingJit(h_new_class.Get()); | |
return h_new_class.Get(); | |
} |
art\runtime\class_linker.cc::SetupClass
SetupClass设置类的一些基本字段信息。
void ClassLinker::SetupClass(const DexFile& dex_file, | |
const DexFile::ClassDef& dex_class_def, | |
Handle<mirror::Class> klass, | |
ObjPtr<mirror::ClassLoader> class_loader) { | |
CHECK(klass != nullptr); | |
CHECK(klass->GetDexCache() != nullptr); | |
CHECK_EQ(mirror::Class::kStatusNotReady, klass->GetStatus()); | |
const char* descriptor = dex_file.GetClassDescriptor(dex_class_def); | |
CHECK(descriptor != nullptr); | |
klass->SetClass(GetClassRoot(kJavaLangClass)); | |
uint32_t access_flags = dex_class_def.GetJavaAccessFlags(); | |
CHECK_EQ(access_flags & ~kAccJavaFlagsMask, 0U); | |
klass->SetAccessFlags(access_flags); | |
klass->SetClassLoader(class_loader); | |
DCHECK_EQ(klass->GetPrimitiveType(), Primitive::kPrimNot); | |
mirror::Class::SetStatus(klass, mirror::Class::kStatusIdx, nullptr); | |
klass->SetDexClassDefIndex(dex_file.GetIndexForClassDef(dex_class_def)); | |
klass->SetDexTypeIndex(dex_class_def.class_idx_); | |
} |
延申:mirror命名空间下的类是底层对Java层类的映射,比如:mirror::Class类就是对java.lang.Class类的映射,SetAccessFlags就是对Class类的accessFlags字段赋值。
art\runtime\class_linker.cc::InsertClass
InsertClass函数判断类是否在列表中:
- 如果在列表中,则直接返回;
- 如果没有,则添加到列表。
mirror::Class* ClassLinker::InsertClass(const char* descriptor, ObjPtr<mirror::Class> klass, size_t hash) { | |
if (VLOG_IS_ON(class_linker)) { | |
ObjPtr<mirror::DexCache> dex_cache = klass->GetDexCache(); | |
std::string source; | |
if (dex_cache != nullptr) { | |
source += " from "; | |
source += dex_cache->GetLocation()->ToModifiedUtf8(); | |
} | |
LOG(INFO) << "Loaded class " << descriptor << source; | |
} | |
{ | |
WriterMutexLock mu(Thread::Current(), *Locks::classlinker_classes_lock_); | |
ObjPtr<mirror::ClassLoader> const class_loader = klass->GetClassLoader(); | |
ClassTable* const class_table = InsertClassTableForClassLoader(class_loader); | |
ObjPtr<mirror::Class> existing = class_table->Lookup(descriptor, hash); | |
if (existing != nullptr) { | |
return existing.Ptr(); | |
} | |
VerifyObject(klass); | |
class_table->InsertWithHash(klass, hash); | |
if (class_loader != nullptr) { | |
// This is necessary because we need to have the card dirtied for remembered sets. | |
Runtime::Current()->GetHeap()->WriteBarrierEveryFieldOf(class_loader); | |
} | |
if (log_new_roots_) { | |
new_class_roots_.push_back(GcRoot<mirror::Class>(klass)); | |
} | |
} | |
if (kIsDebugBuild) { | |
// Test that copied methods correctly can find their holder. | |
for (ArtMethod& method : klass->GetCopiedMethods(image_pointer_size_)) { | |
CHECK_EQ(GetHoldingClassOfCopiedMethod(&method), klass); | |
} | |
} | |
return nullptr; | |
} |
art\runtime\class_linker.cc::LoadClass
LoadClass函数获取了dex文件中的classData部分,然后去调用LoadClassMembers
void ClassLinker::LoadClass(Thread* self, | |
const DexFile& dex_file, | |
const DexFile::ClassDef& dex_class_def, | |
Handle<mirror::Class> klass) { | |
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); | |
} |
art\runtime\class_linker.cc::LoadClassMembers
LoadClassMembers函数主要逻辑是遍历类中的所有字段和函数,然后分别调用LoadField,LoadMethod和LinkCode
void ClassLinker::LoadClassMembers(Thread* self, | |
const DexFile& dex_file, | |
const uint8_t* class_data, | |
Handle<mirror::Class> klass){ | |
...... | |
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; | |
} | |
} | |
...... | |
size_t class_def_method_index = 0; | |
uint32_t last_dex_method_index = DexFile::kDexNoIndex; | |
size_t last_class_def_method_index = 0; | |
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++; | |
} | |
...... | |
} |
art\runtime\class_linker.cc::LoadField
LoadField设置ArtField结构中字段的一些值
void ClassLinker::LoadField(const ClassDataItemIterator& it, | |
Handle<mirror::Class> klass, | |
ArtField* dst) { | |
const uint32_t field_idx = it.GetMemberIndex(); | |
dst->SetDexFieldIndex(field_idx); | |
dst->SetDeclaringClass(klass.Get()); | |
dst->SetAccessFlags(it.GetFieldAccessFlags()); | |
} |
art\runtime\class_linker.cc::LoadMethod
LoadMethod函数主要做设置ArtMethod结构的一些属性,比如函数的MethodIdx,CodeItem在dex文件中的偏移,函数的AccessFlag等。
void ClassLinker::LoadMethod(const DexFile& dex_file, | |
const ClassDataItemIterator& it, | |
Handle<mirror::Class> klass, | |
ArtMethod* dst){ | |
uint32_t dex_method_idx = it.GetMemberIndex(); | |
const DexFile::MethodId& method_id = dex_file.GetMethodId(dex_method_idx); | |
const char* method_name = dex_file.StringDataByIdx(method_id.name_idx_); | |
ScopedAssertNoThreadSuspension ants("LoadMethod"); | |
dst->SetDexMethodIndex(dex_method_idx); | |
dst->SetDeclaringClass(klass.Get()); | |
dst->SetCodeItemOffset(it.GetMethodCodeItemOffset()); | |
dst->SetDexCacheResolvedMethods(klass->GetDexCache()->GetResolvedMethods(), image_pointer_size_); | |
uint32_t access_flags = it.GetMethodAccessFlags(); | |
...... | |
dst->SetAccessFlags(access_flags); | |
} |
延申:ArtMethod是存储Java函数在虚拟机内相关信息的结构,它不同于mirror命名空间下的Method类,ArtMethod在Java层没有类与之直接映射。
art\runtime\class_linker.cc::LinkCode
LinkCode函数主要功能是判断代码是否编译从而为函数设置入口代码。
static void LinkCode(ClassLinker* class_linker, | |
ArtMethod* method, | |
const OatFile::OatClass* oat_class, | |
uint32_t class_def_method_index){ | |
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. | |
const OatFile::OatMethod oat_method = oat_class->GetOatMethod(class_def_method_index); | |
oat_method.LinkMethod(method); | |
} | |
// Install entry point from interpreter. | |
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()) { | |
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)); | |
} | |
} | |
} |