写点什么

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

评论

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

在Python中实现条形图动态追赶动画效果

申公豹

Python

WorkPlus打造企业即时通讯平台,助力高效沟通与协作

WorkPlus

Python从基础到进阶字符串验证

申公豹

Python

Python图像处理:批量添加水印的优雅实现与进阶技巧

申公豹

Python

手把手教你Python圣诞主题绘图

申公豹

Python

文心一言 VS 讯飞星火 VS chatgpt (163)-- 算法导论13.1 3题

福大大架构师每日一题

福大大架构师每日一题

WorkPlus一站式协同解决方案,助力企业降本增效

WorkPlus

掌握进阶:高级功能、图表定制与地理数据绘制

申公豹

Python

cmake | AI工程化部署

AIWeker

c AI AI工程化部署

FlowJo 10 for Mac特别版:流式细胞分析的强大助手

iMac小白

个人技术成长方面的心得体会:边缘计算之旅

农夫三拳

Python文件操作指南:读写、异常处理与上下文管理器详解

申公豹

Python

Seaborn 数据可视化全攻略:从基础到高级实战

申公豹

Python

移动UI交互设计与动效制作

坚果

设计 UI 坚果派

利用开源框架BMF实现高效视频处理

Geek-yan

基于Vue.js和Spring Boot的口罩自助售卖系统:设计、实现与技术深度解析

申公豹

开发

构建高效、安全的在线考试系统:基于Spring Boot和Vue的前后端分离之道

申公豹

E3PO: 探索360°视频流传输的未来

不会算法。

项目分享:AIGC技术在智能教学生成中的应用

YoLo

AI

深入学习Python与Vscode环境的安装与配置

申公豹

Python

多表格文件单元格平均值计算实例解析

申公豹

Python

记一次本地环境启动不了Mysql的故障

DoHornBurg

Java MySQL hyper-v

WorkPlus十年铸剑,成就千万级用户信赖与认可

WorkPlus

HarmonyOS的功能及场景应用

芯动大师

基于STM32的物联网节点设计与实现-传感器数据采集与无线通信

申公豹

嵌入式

WorkPlus Meet实现企业局域网视频会议的领先解决方案

WorkPlus

WorkPlus企业即时通讯系统的领先者,提升沟通效率的利器

WorkPlus

WorkPlus超级APP助力企业节省IT人力成本,实现快速移动化

WorkPlus

鸿蒙 API 9工程转换为API 10工程

坚果

HarmonyOS OpenHarmony

用纯 Python 打造的轻量级 Excel 到 Markdown 转换工具

申公豹

Python

简洁灵活:Python中基于字段的不使用元类的ORM实现

申公豹

Python

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