红帽白皮书新鲜出炉!点击获取,让你的云战略更胜一筹! 了解详情
写点什么

Android ClassLoader 加载过程源码分析

  • 2019-07-18
  • 本文字数:7217 字

    阅读完需:约 24 分钟

Android ClassLoader加载过程源码分析

详细分析 ClassLoader 加载原理

ClassLoader 的继承关系如下:



这里我们主要分析一下 BaseDexClassLoader.findClass()ClassLoader.loadClass()两个函数在系统中是怎么进行查找 class 的过程。


我们看一下系统加载类ClassLoader.loadClass()函数实现代码,在ClassLoader.java中:


    protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {            // 首先 检测是否已经加载过            Class<?> c = findLoadedClass(name);            if (c == null) {                try {                    if (parent != null) {                        //去调用父类的loadClass                        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) { //未找到的情况下,使用findClass在当前dex查找 c = findClass(name); } } return c; }
protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
复制代码


  • 1, loadClass()先调用findLoadedClass()来判断当前类是否已加载;

  • 2, 未查找到递归去父类中查找是否加载到缓存;

  • 3, 均未缓存,去BootClassLoader中查找;

  • 4, 以上未发现,自顶级父类依次向下查找,调用findClass()查找当前 dex。

findLoadedClass 函数分析

下图为findLoadedClass()的调用流程;根据调用流程图配合源代码进行详细的分析原理。



下面介绍对应的源代码实现部分:


    protected final Class<?> findLoadedClass(String name) {        ClassLoader loader;        if (this == BootClassLoader.getInstance())            loader = null;        else            loader = this;        return VMClassLoader.findLoadedClass(loader, name);    }
复制代码


函数最终统一调用VMClassLoader.findLoadedClass()进行查找类。


native static Class findLoadedClass(ClassLoader cl, String name);
复制代码


实现在java_lang_VMClassLoader.cc文件中。


static jclass VMClassLoader_findLoadedClass(JNIEnv* env, jclass, jobject javaLoader,jstring javaName) {  ....  ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(javaLoader);  ClassLinker* cl = Runtime::Current()->GetClassLinker();
ObjPtr<mirror::Class> c = VMClassLoader::LookupClass(cl, soa.Self(), descriptor.c_str(), descriptor_hash, loader); if (c != nullptr && c->IsResolved()) { return soa.AddLocalReference<jclass>(c); } ... if (loader != nullptr) { // Try the common case. StackHandleScope<1> hs(soa.Self()); c = VMClassLoader::FindClassInPathClassLoader(cl, soa, soa.Self(), descriptor.c_str(), descriptor_hash, hs.NewHandle(loader)); if (c != nullptr) { return soa.AddLocalReference<jclass>(c); } }
return nullptr;}
static mirror::Class* LookupClass(ClassLinker* cl, Thread* self, const char* descriptor, size_t hash, ObjPtr<mirror::ClassLoader> class_loader) REQUIRES(!Locks::classlinker_classes_lock_) REQUIRES_SHARED(Locks::mutator_lock_) { return cl->LookupClass(self, descriptor, hash, class_loader); } static ObjPtr<mirror::Class> FindClassInPathClassLoader(ClassLinker* cl, ScopedObjectAccessAlreadyRunnable& soa, Thread* self, const char* descriptor, size_t hash, Handle<mirror::ClassLoader> class_loader) REQUIRES_SHARED(Locks::mutator_lock_) { ObjPtr<mirror::Class> result; if (cl->FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result)) { return result; } return nullptr; }
复制代码


上述代码findLoadedClass()分为两步;


  • 1,通过class_linker_-&gt;Lookupclass()进行查找加载类;

  • 2,如果没找到再通过class_linker_-&gt;FindClassInPathClassLoader()进行查找。


class_linker_在虚拟机的启动startVM()函数的时候进行的初始化。

Runtime::class_linker_Runtime::Init()函数的时候做的初始化。


  if (UNLIKELY(IsAotCompiler())) {    class_linker_ = new AotClassLinker(intern_table_);  } else {    class_linker_ = new ClassLinker(intern_table_);  }
复制代码


继续来分析ClassLinker::LookupClass()函数的具体实现:


mirror::Class* ClassLinker::LookupClass(Thread* self,                                        const char* descriptor,                                        size_t hash,                                        ObjPtr<mirror::ClassLoader> class_loader) {  ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);  ClassTable* const class_table = ClassTableForClassLoader(class_loader);  if (class_table != nullptr) {    ObjPtr<mirror::Class> result = class_table->Lookup(descriptor, hash);    if (result != nullptr) {      return result.Ptr();    }  }  return nullptr;}
复制代码


LookupClass()函数通过class_loader是否为nullptrnullptr使用boot_class_table_来获取class_table, 否则获取当前ClassLoaderClassTableclass_table存放当前已经加载过的 class,其实可以理解为 class cache。如何进行 dex 解析和 aot 等加载系统类和解析映射到内存中的不在此处展开分析。可以了解 art 虚拟机启动进行详细分析。

findClass()函数分析

下图是 findClass 的调用流程;根据调用流程图配合下面的代码进行详细的分析了解:



下面我们介绍对应的源代码实现部分。


findClass()函数在BaseDexClassLoader.java实现, 该函数主要做的事情就是在当前 dex 中查找类。如果类在当前 dex 中即返回。


代码如下:


 @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();        Class c = pathList.findClass(name, suppressedExceptions);        if (c == null) {            ...            throw cnfe;        }        return c;    }
复制代码


pathList类型为DexPathList用来保存dexfile文件的句柄等 dex 的操作。pathList.findClass()实现在当前 dex 中查找类, pathListnew DexClassLoader()构造时初始化。


  public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String librarySearchPath, ClassLoader parent) {        ...        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null);        ...    }
复制代码


DexPathList.java


public DexPathList(ClassLoader definingContext, String dexPath,            String librarySearchPath, File optimizedDirectory) {
... this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // save dexPath for BaseDexClassLoader this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext);
this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);
if (suppressedExceptions.size() > 0) { this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); } else { dexElementsSuppressedExceptions = null; } }
复制代码


dexElements数组保存 dexfile 文件句柄。具体实现在makeDexElements()函数中调用loadDexFile()函数加载 dex。该函数实现:


DexFile.javaprivate static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, Element[] elements) throws IOException {        if (optimizedDirectory == null) {            return new DexFile(file, loader, elements);        } else {            String optimizedPath = optimizedPathFor(file, optimizedDirectory);            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);        }    }
复制代码


DexFile.loadDex()进行解析加载 dex 文件。关键代码如下:


private DexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {    ...    mCookie = openDexFile(sourceName, outputName, flags, loader, elements);    mInternalCookie = mCookie;    mFileName = sourceName;    ...}
private static Object openDexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException { // Use absolute paths to enable the use of relative paths when testing on host. return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags,loader,elements);}
private static native Object openDexFileNative(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements);
复制代码


最终打开dexfile是通过native方法实现,并且返回mCookie, mCookie类型是int用来标识dex的唯一性。 openDexFileNative()实现代码:


//`dalvik_system_DexFile.cc`static jobject DexFile_openDexFileNative(JNIEnv* env,                                         jclass,                                         jstring javaSourceName,                                         jstring javaOutputName,                                         jint flags ATTRIBUTE_UNUSED,                                         jobject class_loader,                                         jobjectArray dex_elements){  ...  Runtime* const runtime = Runtime::Current();  ClassLinker* linker = runtime->GetClassLinker();    ...
dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), class_loader, dex_elements, /*out*/ &oat_file, /*out*/ &error_msgs); ....}
复制代码


上述代码通过aotManager打开并返回mCookie,进一步的打开实现不在此处展开。即上述已经填充elements[],下面开始展开pathList.findClass()函数的查找方式。


    //BaseDexClassLoader.java    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; }
复制代码


findClass()会遍历elements[], 每个element保存了 dex 的DexFile句柄,然后调用loadClassBinaryName()函数进行当前 dex 查找类。


//DexPathList.java  public Class<?> findClass(String name, ClassLoader definingContext,          List<Throwable> suppressed) {      return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;  }
复制代码


  public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {      return defineClass(name, loader, mCookie, this, suppressed);  }
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; }
复制代码


真正去 dex 或者内存中查找类的函数在nativedefineClassNative()实现, 我们来分析一下真正的实现过程:


private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)
//dalvik_system_DexFile.ccstatic 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)) { ... return nullptr; }
ScopedUtfChars class_name(env, javaName); ...
const std::string descriptor(DotToDescriptor(class_name.c_str())); const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); for (auto& dex_file : dex_files) { ... ClassLinker* class_linker = Runtime::Current()->GetClassLinker(); 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()); .... return soa.AddLocalReference<jclass>(result); } } ... return nullptr;}
复制代码


通过Runtime拿到当前的ClassLinker对象,然后通过class_linker-&gt;DefineClass()在当前 dex 中进行查找类。然后把找到的类通过class_linker-&gt;InsertDexFileInToClassLoader()插入到 class_table 中进行缓存,返回查找到的类。这里不进一步展开分析。


Android ClassLoader 加载过程的源代码分析到此已经分析得差不多了,如果想深入地了解具体原理,可以自己看源代码的实现。本文就介绍到这里。初次写技术分享的文章,如有错误请指正,感谢!


本文转载自公众号 360 技术(ID:qihoo_tech)


原文链接


https://mp.weixin.qq.com/s/31MGkrTTjiHfXffUDydONg


2019-07-18 08:003324

评论

发布
暂无评论
发现更多内容

一文读懂AQS的前世今生

是月月啊2023

Java 面试题

2023 Flink Forward Asia 参会指南来啦!

Apache Flink

大数据 flink

喜报!MIAOYUN入选成都高新区“瞪羚企业”

MIAOYUN

云计算 MIAOYUN 瞪羚企业 成都高新梯度培育企业 企业资质

Amazon WorkSpaces 现在提供使用 WorkSpaces 流协议(WSP)的 Web Access

亚马逊云科技 (Amazon Web Services)

Amazon WorkSpaces

14 | 排序优化:如何实现一个通用的、高性能的排序函数.md

鲁米

跨界-我今年最难的一件事

学渣汪在央企打怪升级

🎉开发者的福音:TinyVue 组件库文档大优化!类型更详细,描述更清晰!

Kagol

开源 Vue 前端 UI组件库

我的2023年度关键词:迎接不一样的挑战,充实自我

攻城狮Wayne

#技术人的2023总结

【Android】深入Binder拦截

iofomo

android 拦截器 binder

亚马逊云科技 -- AIGC时代的数椐基础设施

追风少年

Amazon

深度|低代码开发平台和微服务架构的优势与挑战

codebee

DDD 低代码 微服务、

Groovy StringBuilder类踩坑

FunTester

wing一款轻量快捷的团队开发工具

iofomo

Python 跨平台 开发工具

Wireshark中的TCP协议包分析

小齐写代码

Databend 如何利用 GPT-4 进行质量保证

Databend

走心推荐!10款能让你的企业管理事半功倍的咨询软件。

彭宏豪95

科技 在线白板 咨询 在线协作 效率软件

JDK1.8 ConcurrentHashMap 核心源码(面试重点)

是月月啊2023

多邻国还是流利说

escray

技术人写作 21 天技术人写作行动营 21 天

SQL ALTER TABLE 语句- 灵活修改表结构和数据类型

小万哥

MySQL 数据库 sql 程序员 后端开发

Native Drawing开发指导,实现HarmonyOS基本图形和字体的绘制

HarmonyOS开发者

HarmonyOS

“敏捷教练进阶课程”2024年1月27-28日 · A-CSM认证在线周末班【分时段模块化教学】CST导师亲授

ShineScrum捷行

敏捷教练

一文让你知道企业真正需要一个怎样的远程协同运维平台?

行云管家

运维 远程运维 远程协同

写作训练营打卡1--最喜欢的极客时间作者

Avril

另一种学习 Linux kernel 的方式 —— UML

袁世超

UML Linux Kenel

使用 Kubernetes Agent Server 实现 GitOps

极狐GitLab

Kubernetes DevOps gitlab gitops workflow

ERP和MES的区别与联系,这篇接地气的文章终于讲明白了!

优秀

MES系统 mes ERP系统 mes和erp区别

服务器异常问题排查指南

BeyondLife

trouble shooting 线上问题排查 服务器异常排查 linux 命令

耗时三年开源的H5商城,强烈推荐

越长大越悲伤

Java 开源 springboot

英特尔李映:开源开放是软件生态开拓进取的原动力

E科讯

GPU在元宇宙中的作用—元宇宙云端解决方案

3DCAT实时渲染

元宇宙 实时渲染

[译]你需要知道的CSS属性isolation

南城FE

CSS 前端 布局

Android ClassLoader加载过程源码分析_语言 & 开发_孙俊飞_InfoQ精选文章