详细分析 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_->Lookupclass()
进行查找加载类;2,如果没找到再通过
class_linker_->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
是否为nullptr
,nullptr
使用boot_class_table_
来获取class_table
, 否则获取当前ClassLoader
的ClassTable
。 class_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 中查找类, pathList
在new 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.java
private 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 或者内存中查找类的函数在native
中defineClassNative()
实现, 我们来分析一下真正的实现过程:
private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)
//dalvik_system_DexFile.cc
static 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->DefineClass()
在当前 dex 中进行查找类。然后把找到的类通过class_linker->InsertDexFileInToClassLoader()
插入到 class_table 中进行缓存,返回查找到的类。这里不进一步展开分析。
Android ClassLoader 加载过程的源代码分析到此已经分析得差不多了,如果想深入地了解具体原理,可以自己看源代码的实现。本文就介绍到这里。初次写技术分享的文章,如有错误请指正,感谢!
本文转载自公众号 360 技术(ID:qihoo_tech)。
原文链接:
https://mp.weixin.qq.com/s/31MGkrTTjiHfXffUDydONg
更多内容推荐
12|依赖注入与路由:框架能够为我们解决什么问题?
这节课,我将和你一起学习路由及注入框架的设计思想以及实现原理,然后结合这些框架进一步改造Sharing项目。
2023-03-08
2、SpringBoot- 启动流程源码解析
2023-09-28
前端一面常考手写面试题整理
实现代码如下:
2023-02-13
【DBA100 人】台枫:DBA 不仅要懂运维还得懂代码
引言
2022-10-14
自主创新、领先一代,星环科技成功登陆科创板
10月18日,“国产大数据基础软件”星环信息科技(上海)股份有限公司(简称“星环科技”,证券代码“688031”)在科创板上市,宣告正式开启新征程。
2022-10-21
cstdio 的源码学习分析 10- 格式化输入输出函数 fprintf--- 宏定义 / 辅助函数分析 06
fprintf函数的实现vfprintf中包含了相当多的宏定义和辅助函数,接下来我们一起来分析一下它们对应的源码实现。
2022-10-18
7. 日志收集与分析
2023-09-27
从 React 源码分析看 useEffect
这里不再讲useLayoutEffect,它和useEffect的代码是一样的,区别主要是:
2023-02-06
SLF4J 门面日志框架源码探索 | 京东云技术团队
我们通过代码入手,层层加码,直观感受SLF4J打印日志,并跟踪代码追本溯源。主要了解,SLF4J是如何作为门面和其他日志框架进行解耦。
2023-06-21
深度学习应用篇 - 计算机视觉 -OCR 光学字符识别 [7]:OCR 综述、常用 CRNN 识别方法、DBNet、CTPN 检测方法等、评估指标、应用场景
OCR(Optical Character Recognition,光学字符识别)是指对图像进行分析识别处理,获取文字和版面信息的过程,是典型的计算机视觉任务,通常由文本检测和文本识别两个子任务构成。
2023-06-11
百度 APP iOS 端包体积 50M 优化实践 (三) 资源优化
不管是资源优化还是代码优化,都需要分析Mach-O文件,以获取资源和代码的引用关系,本文先详细介绍Mach-O文件。
2023-06-06
cstdio 的源码学习分析 10- 格式化输入输出函数 fprintf--- 宏定义 / 辅助函数分析 03
fprintf函数的实现vfprintf中包含了相当多的宏定义和辅助函数,接下来我们一起来分析一下它们对应的源码实现。
2022-10-15
【实战篇】通过 SoFlu 构建一个低代码平台实现过程之“表单”
在现代企业开发中,初级开发者难以掌握开发企业级 Java 应用,中高级开发者则被繁琐任务所占据。为了让大家能快速上手可以解决这个难题的“SoFlu 软件机器人”,前蚂蚁金服技术专家杨彪设计了《2 小时学会 10 个组件“取代”传统 Java 开发》课程,本节为该系列课程的第九讲。
2. ORM 框架概览:Beego ORM 分析
2023-09-26
07|类加载子系统(下):如何打造一个属于你的类加载器?
如何打造一个属于你的类加载器?
2023-09-04
国内外低代码开发平台优劣势一览
随着数字化转型的推进,低代码开发平台正变得越来越受欢迎。在国内外市场上,已经出现了许多低代码开发平台。目前国内主流的低代码开发平台有:宜搭、简道云、明道云、云程、氚云、用友 YonBuilder、道一云、JEPaaS、华炎魔方、搭搭云、JeecgBoot 、RuoYi 等
2023-03-31
C++ 学习 ---cstdio 的源码学习分析 07- 重新打开文件流函数 freopen
stdio.h中定义了一系列文件访问函数(fopen,fclose,fflush,freopen,setbuf,setvbuf),接下来我们一起来分析一下freopen对应的源码实现。
2022-10-08
【7.7-7.14】写作社区优秀技术博文一览
为了让更多的创作者有展示自己的舞台,为了让更多优质内容有发光发热的机会,InfoQ 官方写作社区会时不时向大家推荐近期发布在社区的优质作品。
2023-07-14
17|Adaptive 适配:Dubbo 的 Adaptive 特殊在哪里?
我们从 Adaptive 注解为什么被 Dubbo 框架诸多地方使用的疑问开始,跟踪 getAdaptiveExtension 方法的源码,最后得出@Adaptive 注解其实是生成了一个自适应的代理类,每个 SPI 接口都有且仅有一个自适应扩展点。
2023-01-25
软件测试 / 测试开发 | Frida 实现 Hook 功能的强大能力
Frida 通过 C 语言将 QuickJS 注入到目标进程中,获取完整的内存操作权限,达到在程序运行时实时地插入额外代码和数据的目的。官方将调用代码封装为 python 库,当然你也可以直接通过其他的语言调用 Frida 中的 C 语言代码进行操作。
2023-03-02
推荐阅读
第 04 节 利益信任:建立信任最有效的方法
2023-10-17
从大模型到 MaaS 的新生态
2023-10-30
06. 如何给部门人员,做现状分析并对其赋能?
2023-10-17
9、源码阅读之 mapper 代理底层源码
2023-09-28
浅析斐波那契数列在代码中的应用
2023-10-13
低代码开发平台有什么优势?
2023-12-05
交易日均千万订单的存储架构设计与实践 | 京东物流技术团队
2023-09-25
电子书
大厂实战PPT下载
换一换 赵兵 | 知乎 大数据基础架构开发工程师
谭霖 | 美团 集群调度系统负责人
张柳青 | 百度 资深研发工程师
评论