写点什么

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:003585

评论

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

MySQL information_schema 系统库介绍

Simon

MySQL

ipfs矿机公司哪家好?ipfs矿机公司实力排行?

分布式存储 Filecoin ipfs挖矿 ipfs矿机 ipfs矿商排名

模块五:微博评论模块高性能高可用计算架构设计

kk

架构实战营

英特尔CEO帕特·基辛格:面向未来的数字化需求,推进未来计算创新、探索与颠覆

科技新消息

小米和网易两位资深工程师联合编写的HBASE原理与实践PDF

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

膜拜!终于拿到了美团大佬分享的Netty源码剖析与应用PDF

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

微信架构图设计&“学生管理系统”毕设架构

Imaginary

LVS 学习: netfilter 与 ipvs 无秘密

绅鱼片

Linux 负载均衡 LVS Netfilter IPVS

量化策略APP系统开发,马丁策略交易平台

13530558032

亚信数据库AIDB通过统信UOS认证,国产自主可控项目新选择

亚信AntDB数据库

国产化 国产数据库 亚信数据库AIDB

小布助手在面向中文短文本的实体链指比赛中的实践应用

OPPO小布助手

人工智能 算法 模型训练 智能助手 短文本

迅雷不及掩耳盗铃

escray

生活记录 8月日更 搜房记

手把手教你15分钟搭建人脸戴口罩识别软硬件系统

百度大脑

人工智能 EasyDL

Linux内核源码分析方法—程序员进阶必备

Linux服务器开发

操作系统 Linux内核 内核源码 底层原理 内核开发

中国法定数字货币(DCEP)全面启航!全国普及势在必行

CECBC

80W美团架构师整理分享出了Spring5企业级开发实战文档

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

搭建太阳系3D可视化平台,科普宇宙的未知奥秘

一只数据鲸鱼

科普 数据可视化 智慧宇宙 太空

ipfs投资者靠什么赚钱?投资ipfs要多少钱?

投资ipfs要多少钱 ipfs投资者靠什么赚钱

python通过Matplotlib绘制常见的几种图形

Python研究者

8月日更

上游思维:凭一己之力能做些什么?

石云升

读书笔记 8月日更 上游思维

干货!DataPipeline2021数据管理与创新大会全篇划重点

DataPipeline数见科技

大数据 数据融合 数据管理

阿里专家分享的SpringCloudNginx高并发核心文档

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

spring-boot 2.5.4,nacos 作为配置、服务发现中心,Cloud Native Buildpacks 打包镜像,GitLab CI/CD

Zhang

gitlab nacos CI/CD spring-boot 2.5.4 CNB

BuildPacks 打包

Zhang

Docker image CNB OCI

InnoDB 表空间

leonsh

MySQL innodb 表空间

做正确的事情,而不是把事情做正确

非著名程序员

个人成长 提升认知 认知提升 8月日更

阿里专家分享内部绝密RocketMQ核心原理与最佳实践PDF

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

探索技术与应用融合的区块链 实现产业良性发展

CECBC

BI软件漫谈

格林海文

BI Tableau 帆软

快手基于 Flink 构建实时数仓场景化实践

阿里云大数据AI技术

Tron波场链智能合约系统开发案例|波场链源码搭建

Geek_23f0c3

TRONex波场智能合约 DAPP智能合约交易系统开发 波场DAPP

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