关于Java中几种loadClass的讨论
java中有几类加载class的方法,本文针对这几个方法,就行源码导读。
本文的native源码来源于android4.1.2源码。
1. Class.forName(className, true, classLoader);
clazz = Class.forName(className, true, classLoader);
className:要加载的类的全路径名。
classLoader:类加载器。
这个函数会调用native方法,源码:java_lang_Class.cpp
static void Dalvik_java_lang_Class_classForName(const u4* args, JValue* pResult)
{
StringObject* nameObj = (StringObject*) args[0];
bool initialize = (args[1] != 0);
Object* loader = (Object*) args[2];
RETURN_PTR(dvmFindClassByName(nameObj, loader, initialize));
}
这里调用的是dvmFindClassByName(nameObj, loader, initialize),重点在这句,由于此函数代码比较多,我只罗列了关键语句:
ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,
bool doInit)
{
ClassObject* clazz = NULL;
char* name = NULL;
char* descriptor = NULL;
......
// 初始化的参数在这里体现
if (doInit)
clazz = dvmFindClass(descriptor, loader);
else
clazz = dvmFindClassNoInit(descriptor, loader);
.......
}
由于dvmFindClass会先调用dvmFindClassNoInit,下面优先分析dvmFindClassNoInit
由于代码比较多,下面只列出了调用函数关系:
dvmFindClassNoInit-->findClassFromLoaderNoInit-->dvmLookupClass-->dvmHashTableLookup
关键看dvmHashTableLookup这个函数:
void* dvmHashTableLookup(HashTable* pHashTable, u4 itemHash, void* item,
HashCompareFunc cmpFunc, bool doAdd)
{
pEntry = &pHashTable->pEntries[itemHash & (pHashTable->tableSize-1)];
pEnd = &pHashTable->pEntries[pHashTable->tableSize];
while (pEntry->data != NULL) {
if (pEntry->data != HASH_TOMBSTONE &&
pEntry->hashValue == itemHash &&
(*cmpFunc)(pEntry->data, item) == 0) // 这里是需要ClassObject的关键点
}
return result;
}
此函数寻找ClassObject的逻辑是:
通过classname生成hash值,在HashTable中寻找此类的ClassObject,而这个HashTable保存在全局变量DvmGlobals中,ClassObject也就是C++中的对象,在虚拟机的堆中,classname和classloader两者对应唯一的一个ClassObject。看一下关键的比较函数cmpFunc如何判定此ClassObject是classname要寻找的Object
match = (strcmp(clazz->descriptor, pCrit->descriptor) == 0 &&
(clazz->classLoader == pCrit->loader ||
(pCrit->loader != NULL &&
dvmLoaderInInitiatingList(clazz, pCrit->loader)) ));
这里通过clazz->descriptor,classLoader 决定是否是要寻找的类对象。
所以有关java中ClassLoader内容,不同的类在不同的ClassLoader中加载,对应的不同的ClassObject,有此函数可以得出此结论。
回到刚才的函数,如果通过dvmLookupClass寻找不到对应的ClassObject,怎么处理?
会调用Invoke loadClass()进行class加载和初始化。
const Method* loadClass =
loader->clazz->vtable[gDvm.voffJavaLangClassLoader_loadClass];
JValue result;
dvmCallMethod(self, loadClass, loader, &result, nameObj);
clazz = (ClassObject*) result.l;
这段函数还是比较重要的,通过ClassLoader,进行重新加载,dvmCallMethod是java调用native的入口。
2. ClassLoader Class<?> loadClass(String className, boolean resolve)
这个函数可能会被表面定义混淆,这个函数的逻辑也是先在HashTable中需要className对应的ClassObjet,如果寻找不到,在进行加载和初始化。
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null) {
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
// Don't want to see this.
}
if (clazz == null) {
clazz = findClass(className);
}
}
return clazz;
}
一、首先看Class<?> clazz = findLoadedClass(className);这句
对应的native源码在java_lang_VMClassLoader.cpp中。
static void Dalvik_java_lang_VMClassLoader_loadClass(const u4* args,
JValue* pResult)
{
StringObject* nameObj = (StringObject*) args[0];
bool resolve = (args[1] != 0);
ClassObject* clazz;
clazz = dvmFindClassByName(nameObj, NULL, resolve);
assert(clazz == NULL || dvmIsClassLinked(clazz));
RETURN_PTR(clazz);
}
重点看一下dvmFindClassByName
ClassObject* dvmFindClassByName(StringObject* nameObj, Object* loader,
bool doInit)
{
ClassObject* clazz = NULL;
char* name = NULL;
char* descriptor = NULL;
if (doInit) // 这里是这个函数的重点逻辑
clazz = dvmFindClass(descriptor, loader);
else
clazz = dvmFindClassNoInit(descriptor, loader);
}
这里如果查找不到,便会返回null。
二、clazz = parent.loadClass(className, false);
这里是调用classloader的父类进行加载,其加载逻辑相同。
三、 clazz = findClass(className);
如果一和二都不能找到className对应的ClassObject,那么就要通过findClass(className)进行寻找,这个函数的定义在classLoader,而android平台下定义在BaseDexClassLoader,看一下他的函数:
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = pathList.findClass(name);
if (clazz == null) {
throw new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + originalPath);
}
return clazz;
}
这里调用的是pathList.findClass(name);
继续跟踪函数:
public Class findClass(String name) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext);
if (clazz != null) {
return clazz;
}
}
}
return null;
}
这里调用的是dex.loadClassBinaryName(name, definingContext);,对应的native函数为:
dalvik_system_DexFile.cpp
static void Dalvik_dalvik_system_DexFile_defineClass(const u4* args,
JValue* pResult)
{
StringObject* nameObj = (StringObject*) args[0];
Object* loader = (Object*) args[1];
int cookie = args[2];
ClassObject* clazz = NULL;
DexOrJar* pDexOrJar = (DexOrJar*) cookie;
DvmDex* pDvmDex;
char* name;
char* descriptor;
name = dvmCreateCstrFromString(nameObj);
descriptor = dvmDotToDescriptor(name);
ALOGV("--- Explicit class load '%s' l=%p c=0x%08x",
descriptor, loader, cookie);
free(name);
if (!validateCookie(cookie))
RETURN_VOID();
if (pDexOrJar->isDex)
pDvmDex = dvmGetRawDexFileDex(pDexOrJar->pRawDexFile);
else
pDvmDex = dvmGetJarFileDex(pDexOrJar->pJarFile);
/* once we load something, we can't unmap the storage */
pDexOrJar->okayToFree = false;
clazz = dvmDefineClass(pDvmDex, descriptor, loader);
}
clazz = dvmDefineClass(pDvmDex, descriptor, loader);这句是核心,
直接进入Class.cpp中的static ClassObject* findClassNoInit(const char* descriptor, Object* loader,
DvmDex* pDvmDex)函数,这个函数的简单逻辑是dvmLookupClass寻找class,如果找不到,会创建对应的ClassObject,并且添加到Globals中。
clazz = dvmLookupClass(descriptor, loader, true); // 寻找对应的ClassObject
if (clazz == NULL) {
const DexClassDef* pClassDef;
dvmMethodTraceClassPrepBegin();
profilerNotified = true;
#if LOG_CLASS_LOADING
u8 startTime = dvmGetThreadCpuTimeNsec();
#endif
// 通过dex加载对应的类
if (pDvmDex == NULL) {
assert(loader == NULL); /* shouldn't be here otherwise */
pDvmDex = searchBootPathForClass(descriptor, &pClassDef);
} else {
pClassDef = dexFindClass(pDvmDex->pDexFile, descriptor);
}
if (pDvmDex == NULL || pClassDef == NULL) {
if (gDvm.noClassDefFoundErrorObj != NULL) {
/* usual case -- use prefabricated object */
dvmSetException(self, gDvm.noClassDefFoundErrorObj);
} else {
/* dexopt case -- can't guarantee prefab (core.jar) */
dvmThrowNoClassDefFoundError(descriptor);
}
goto bail;
}
/* found a match, try to load it */
clazz = loadClassFromDex(pDvmDex, pClassDef, loader);
if (dvmCheckException(self)) {
/* class was found but had issues */
if (clazz != NULL) {
dvmFreeClassInnards(clazz);
dvmReleaseTrackedAlloc((Object*) clazz, NULL);
}
goto bail;
}
/*
* Lock the class while we link it so other threads must wait for us
* to finish. Set the "initThreadId" so we can identify recursive
* invocation. (Note all accesses to initThreadId here are
* guarded by the class object's lock.)
*/
dvmLockObject(self, (Object*) clazz);
clazz->initThreadId = self->threadId;
/*
* Add to hash table so lookups succeed.
*
* [Are circular references possible when linking a class?]
*/
assert(clazz->classLoader == loader);
if (!dvmAddClassToHash(clazz)) { 添加创建的类到HashTable
/*
* Another thread must have loaded the class after we
* started but before we finished. Discard what we've
* done and leave some hints for the GC.
*
* (Yes, this happens.)
*/
//ALOGW("WOW: somebody loaded %s simultaneously", descriptor);
clazz->initThreadId = 0;
dvmUnlockObject(self, (Object*) clazz);
/* Let the GC free the class.
*/
dvmFreeClassInnards(clazz);
dvmReleaseTrackedAlloc((Object*) clazz, NULL);
/* Grab the winning class.
*/
clazz = dvmLookupClass(descriptor, loader, true);
assert(clazz != NULL);
goto got_class;
}
dvmReleaseTrackedAlloc((Object*) clazz, NULL);
......
}
到此整个流程已经介绍完毕。
由此看出我们直接调用ClassLoader.loadClass要比调用Class.forName更高效。
相关推荐
`ClassLoader`抽象类提供了几个关键方法,如`loadClass()`, `loadClass(String, boolean)`, `findClass()`和`defineClass()`。`loadClass()`是用户调用的接口,而`loadClass(String, boolean)`是内部实现的,通常由...
单例模式、工厂模式、代理模式和适配模式是Java中常用的几种设计模式,它们各有不同的应用场景和优势。理解这些设计模式的原理和实现,能够帮助开发者写出更加模块化和可维护的代码。 最后,理解面向对象编程的设计...
- Java中的几种IO模型,例如NIO(新IO)和AIO(异步IO)。 4. 多线程并发编程: - Java中的并发集合类,如ConcurrentHashMap,以及与传统HashMap的区别。 - synchronized关键字的使用,以及它的不同形式(修饰...
本文将深入探讨Java基础学习中的几个关键知识点,包括Properties的使用、装饰者模式以及缓冲流的运用。 首先,Properties是Java中用于存储键值对的一个类,常用于配置文件的管理。它基于HashTable,提供了一种持久...
在操作Properties文件时,有几种常见的操作: a. 查询键值对: - `getProperty(String key)`:根据指定的键查找属性,如果没有找到则返回null。 - `getProperty(String key, String defaultValue)`:如果找不到键...
这个压缩包“Android高级应用源码-android java 通用代码,关于用properties存储打印的Log.zip”显然是一个关于如何在Android应用中使用Java的Properties类来管理日志打印的示例。下面我们将详细讨论这个知识点。 ...
- **ID生成方案**:介绍在分布式环境中保证ID全局唯一性的几种常见方案,如UUID、Snowflake算法等。 - **具体实现**:结合分库分表的场景,探讨如何在实际项目中实现全局唯一ID的生成与分配。 #### 6. RPC底层通讯...
##### 食谱7: “Could not find or load main class”错误解读 - **知识点**: 类加载机制、编译器错误。 - **解释**: 这个错误通常出现在Java应用程序启动时无法找到主类的情况下。本食谱详细解释了类加载的过程,并...
在Java Web开发中,Spring MVC框架是用于构建高效、可扩展的Web应用程序的首选工具。在处理JSON数据时,Spring MVC提供了丰富的支持。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于前后...
总结起来,实现"快速预览手机中图片资源"涉及到以下几个关键点:扫描手机图片、使用图片加载库、使用GridView展示图片,以及进行性能优化。通过以上步骤,我们可以构建一个高效、流畅的图片预览功能,满足用户的日常...
在IT领域,OCR(Optical Character Recognition,光学字符识别)技术是一种将图像中的文本转换为机器编码文本的技术,广泛应用于文档扫描、表格识别、印刷体和手写体识别等场景。微软MODI(Microsoft Office ...
20. Java源程序编译后生成的字节码文件扩展名为.class。 21. Python的数值运算操作符不需要引用第三方库math,Python提供了内置的数学运算符,并且有增强赋值操作符。 22. 菜单通常由菜单标题、菜单项和菜单栏组成...
24. **Servlet中获取PrintWriter对象的方式**:题目提供了获取PrintWriter对象的几种方式,并要求选出正确的。正确答案为C,即通过`response.getWriter()`获得。 ### Windows中的文件操作 25. **Windows中的文件...
### Hibernate对象中的几种状态 #### 状态介绍 1. **Transient**:当一个对象尚未被持久化,即未被Session管理时的状态。 2. **Persistent**:当一个对象已经被Session管理,且其状态已经被持久化到数据库时的状态...
在本例中,存在以下几种消息类型: 1. **欢迎消息(Welcome Message)**:这是我们的核心消息类型,用于向用户表示欢迎。为了实现个性化,我们可以预先定义一个欢迎消息的模板,然后根据需要填充特定的信息,如...
这是 HashMap 中比较核心的几个成员变量;看看分别是什么意思? 初始化桶大小,因为底层是数组,所以这是数组默认的大小。 桶最大值。 默认的负载因子(0.75) table 真正存放数据的数组。 Map 存放数量的大小。 桶...