写点什么

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

评论

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

从 DevOps 到平台工程:软件开发的新范式

SEAL安全

DevOps 软件研发 平台工程

高级修图软件:Affinity Photo中文Mac版

真大的脸盆

Mac 图像处理 图像编辑 编辑图像 处理图像工具

旭阳数字郗维宝:数智化转型助力焦化企业打破行业困境

用友BIP

升级企业数智化底座 2023用友技术大会

Java面试题1000+附答案大全(合适各级Java开发人员)

采菊东篱下

Java

苹果系统更新:MacOS 11-13.x(PKG系统安装包及IPSW固件)

Rose

mac系统 macOS 13 Ventura 苹果最新系统 苹果系统下载

BATJ架构师首推!分布式事务原理与实战,出神入化

程序知音

Java 分布式 java架构 Java进阶 后端技术

VM虚拟机 v13.0.2激活版 for Mac许可秘钥

Rose

VMware Fusion Pro 13 VM虚拟机破解版 Mac虚拟机 VMware Fusion激活秘钥

深入浅出 OkHttp 源码解析及应用实践

vivo互联网技术

okhttp 拦截器 源代码

阿里云AnalyticDB(ADB) + LLM:构建AIGC时代下企业专属ChatBot

科技热闻

HashMap 底层是如何实现的?

javacn.site

java面试

CDN网关超大range计算方法

天翼云开发者社区

CDN

【5.12-5.19】写作社区优秀技术博文一览

InfoQ写作社区官方

热门活动 优质创作周报

「聊天机器人构建、智能文档问答」大模型应用开发实操课程来了

飞桨PaddlePaddle

AIGC Prompt

浅谈基于Shapley值的数据融合反欺骗数据判断相关

天翼云开发者社区

数据 Shapley

MobTech 秒验|助力预热618

MobTech袤博科技

Photoshop 2023 如何切换语言?

Rose

Photoshop 2023下载 如何切换PS语言 PS 2023破解

什么是研发 Lead Time?我悟了!

LigaAI

DevOps 敏捷开发 研发速率 研发效能管理 企业号 5 月 PK 榜

【GPT-4理论篇-1】GPT-4核心技术探秘 | 京东云技术团队

京东科技开发者

人工智能 openai GPT-4 企业号 5 月 PK 榜

标签系列:标签管理平台的架构与设计

Taylor

数据 标签 数据管理 标签体系

Scrum进入疲惫期?三点帮你走出困境

敏捷开发

Scrum 敏捷开发 软件开发 Scrum Master 疲倦期

上海丨阿里云 Serverless 技术实战营邀你来玩!

Serverless Devs

阿里云 Serverless 中间件

2023年互联网大厂高频Java面试题(附答案)整理总结

架构师之道

Java 面试

Java性能分析工具JProfiler注册码下载

Rose

JProfiler激活码 JProfiler Mac永久激活版 Java 分析器 JProfiler中文版 JProfiler下载

奋楫扬帆 津鸿智放丨中软国际携手深开鸿亮相第七届世界智能大会

科技热闻

打造绿色低碳存储方案,助推数据中心绿色长“存”

天翼云开发者社区

CIO

原型设计Axure RP 9中文授权码【Mac/win】

Rose

Axure RP 9汉化 Axure RP 9授权码 产品原型设计工具 axure rp9下载 axure rp9安装教程

异常体系与项目实践

Java 架构

《好好学习》:如何管理知识?

郭明

JavaScript中eval和with语句如何影响作用域链:探索深度知识

Immerse

中央企业数字化转型专业委员会正式揭牌!

用友BIP

财务管理

用好kafka,你不得不知的那些工具

JAVA旭阳

Java kafka

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