PCon全球产品创新大会最新日程上线,这里直达 了解详情
写点什么

Android ClassLoader 加载过程源码分析

  • 2019 年 7 月 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 年 7 月 18 日 08:002527

评论

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

学习能力

Nydia

靠谱决策的三大法宝:数据,经验和常识

Ian哥

数据 决策 经验 常识

来来来,手摸手写一个hook

全栈潇晨

React React Hooks

了解 Flutter 的Timer类和Timer.periodic【Flutter专题19】

坚果

flutter 28天写作 签约计划第二季 12月日更

2.react心智模型(来来来,让大脑有react思维吧)

zz1998

React

架构师实战营模块一作业

圈圈gor

「架构实战营」

聊聊IT行业的项目管理模式

圣迪

项目管理 敏捷 pmp 开发 瀑布

一对一沟通有必要吗?

Justin

沟通 28天写作

聊聊 Kafka:Producer 源码解析

老周聊架构

「架构实战营」模块一《为何架构设计能力难以提升》作业

DaiChen

作业 模块一 「架构实战营」

老大react说:schedule,我们今年的小目标是一个亿

全栈潇晨

React React Hooks

数据分析从零开始实战专栏导航@老表

老表

Python 数据库 数据分析 pandas 数据分析从零开始实战

跟老表学云服务器开发专栏导航

老表

Python Linux 内容合集 签约计划第二季 跟老表学云服务器

Go语言学习查缺补漏ing Day6

恒生LIGHT云社区

golang 编程语言

如何用Python发送告警通知到钉钉?

老表

Python Linux 定时任务 守护进程 跟老表学云服务器

【LeetCode】二叉搜索树中的搜索Java题解

HQ数字卡

算法 LeetCode 12月日更

模块1

Geek_59dec2

记录:今年最骄傲的一件事

将军-技术演讲力教练

团队基建系列 - 组织知识传承3 破局

Hillz

Prometheus Exporter (二十一)Ceph Exporter

耳东@Erdong

Prometheus Ceph 28天写作 exporter 12月日更

Java jar 如何防止被反编译

xcbeyond

28天写作 12月日更

管人理事

张老蔫

28天写作

《PyTorch 深度学习实战》复习3(下)

IT蜗壳-Tango

28天写作 12月日更

盘点JavaScript哪些常用的字符串对象

前端史塔克

JavaScript 大前端 字符串 基础知识 12月日更

在线MySQL,SQL Server建表语句生成JSON测试数据工具

入门小站

工具

python scrapy极细拆解,打开Spider类看内容,顺手爬了一下优设网

梦想橡皮擦

12月日更

李飞飞力荐:阿里巴巴高可用数据库解决方案

博文视点Broadview

前端开发:详细讲解this指向的相关知识

三掌柜

28天写作 21天挑战 12月日更 12月

人人都能读懂的react源码解析(大厂高薪必备)

zz1998

React React Hooks

Flutter 详解 CupertinoSegmentedControl 分段控制器

阿策小和尚

28天写作 0 基础学习 Flutter 内容合集 签约计划第二季 12月日更

如果TGO是经纪人,我们会怎么办?(9/28)

赵新龙

28天写作

ShadowRealm 与微前端沙箱

ShadowRealm 与微前端沙箱

Android ClassLoader加载过程源码分析-InfoQ