这里讨论的
Class.forName
是Class
类的方法public static Class> forName(String className) throws ClassNotFoundException
这里讨论的
ClassLoader.loadClass
是ClassLoader
类的方法public Class> loadClass(String name) throws ClassNotFoundException
ClassLoader在加载类时的概念区分
Class.forName
和ClassLoader.loadClass
都可以用来进行类型加载,而在Java进行类型加载的时刻,一般会有多个ClassLoader可以使用,并可以使用多种方式进行类型加载。
比如如下代码:
class A {
public void m() {
A.class.getClassLoader().loadClass(“B”);
}
}
在A.class.getClassLoader().loadClass(“B”);
代码执行B的加载过程时,一般会有三个概念上的ClassLoader提供使用。
CurrentClassLoader,称之为当前类加载器,简称CCL,在代码中对应的就是类型A的类加载器。
SpecificClassLoader,称之为指定类加载器,简称SCL,在代码中对应的是 A.class.getClassLoader()
,如果使用任意的ClassLoader进行加载,这个ClassLoader都可以称之为SCL。
ThreadContextClassLoader,称之为线程上下文类加载器,简称TCCL,每个线程都会拥有一个ClassLoader引用,而且可以通过Thread.currentThread().setContextClassLoader(ClassLoader classLoader)
进行切换。
SCL和TCCL可以理解为在代码中使用ClassLoader的引用进行类加载,而CCL却无法获取到其引用,虽然在代码中CCL == A.class.getClassLoader() == SCL。
CCL的加载过程是由JVM运行时来控制的,是无法通过Java编程来更改的。
这里提到了CCL、SCL和TCCL,那么和Class.forName
以及ClassLoader.loadClass
有什么关系呢?因为我们在分析Class.forName
和ClassLoader.loadClass
时将会涉及到这些ClassLoader。
Class.forName与SystemDictionary
Class.forName
是根据给定的类型全名从CCL中加载指定的类型。
实现代码:
@CallerSensitive
public static Class> forName(String className)
throws ClassNotFoundException {
Class> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
Reflection.getCallerClass()
,获取到调用Class.forName
方法的类,隐含意义就是CCL。加载的逻辑在native方法forName0
中定义,也就是forName进行的类加载行为已经脱离了Java代码的控制范围,进入到了Java运行时环境把控的阶段。
以下是JDK实现的部分代码:
JDK7下
Class.c中对应的实现逻辑:
JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
jboolean initialize, jobject loader)
{
// 略
cls = JVM_FindClassFromClassLoader(env, clname, initialize,
loader, JNI_FALSE);
done:
if (clname != buf) {
free(clname);
}
return cls;
}
实现细节在JVM_FindClassFromClassLoader
中定义,可以看到调用Class.forName
会使用JVM_FindClassFromClassLoader
这个函数来进行类型加载,我们需要注意的时clname和loader这两个变量,一个是类的全限定名,另一个是ClassLoader,而Class.forName所使用的ClassLoader是CCL。
在jvm.cpp中FindClassFromClassLoader的对应实现是:
jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init, Handle loader, Handle protection_domain, jboolean throwError, TRAPS) {
// Security Note:
// The Java level wrapper will perform the necessary security check allowing
// us to pass the NULL as the initiating class loader.
klassOop klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);
// 略
}
SystemDictionary
,系统字典,这个数据结构是保存Java加载类型的数据结构,如图1所示。
上图黑色边框中的内容就是SystemDictionary,它是以类的全限定名再加上类加载器作为key,进而确定Class引用。
当在代码中调用Class.forName(String name)或者由运行时Java进行类加载,比如:
public void m() {
B b = new B();
}
对类型B的加载,就是运行时Java进行的类加载。
类型加载时,以ClassLoader和需要加载的类型全限定名作为参数在SystemDictionary中进行查询,如果能够查询到则返回。如果无法找到,则调用loader.loadClass(className)进行加载,这一步将进入到Java代码中。
对于loadClass而言,基本等同于loader.defineClass(loader.getResource(file).getBytes())
,它做了两件事,第一件,通过资源定位到类文件,第二件,将类文件的字节流数组传递给defineClass进行构造Class实例。而defineClass将再一次派发给运行时Java进行执行。
字节流数组经过ClassFileParser进行处理之后,生成了Class实例,在返回Class实例前,Java将name、loader和class的对应关系添加到SystemDictionary中,这样在后续其他类型的加载过程中,就能够快速找到这些类型,避免无谓的defineClass过程。
一个类加载的过程,在运行时Java(JVM)和java代码之间来回切换,有点复杂,我们画一个简单的图来描述主要过程,由于原有的类加载过程中还要处理并发问题,我们将这些内容都去掉,只观察类型加载的主要流程,如图2所示。
上图是一个经过简化后的Class.forName加载过程,这里不再贴代码了,红色的框代表是Java代码,或者说能够在rt.jar中找到的内容,绿色的框代表是JVM的实现代码,是由C和C++实现的,当由Java调用C的代码时,使用绿色的箭头,反之使用红色箭头。以上的方法名和参数都做过简化,并不是真实代码的完全体现,下面说明一下以上需要关注的步骤。
步骤 | 说明 |
---|---|
1 | 调用Class.forName(className) 方法,该方法会调用native的JVM实现,调用前该方法会确定准备好需要加载的类名以及ClassLoader,将其传递给native方法 |
2 | 进入到JVM实现后,首先会在SystemDictionary中根据类名和ClassLoader组成hash,进行查询,如果能够命中,则返回 |
3 | 如果加载到则返回 |
4 | 如果在SystemDictionary中无法命中,将会调用Java代码:ClassLoader.loadClass(类名) ,这一步将委派给Java代码,让传递的ClassLoader进行类型加载 |
5 | 以URLClassLoader为例,ClassLoader确定了类文件的字节流,但是该字节流如何按照规范生成Class对象,这个过程在Java代码中是没有体现的,其实也就是要求调用ClassLoader.defineClass(byte[]) 进行解析类型,该方法将会再次调用native方法,因为字节流对应Class对象的规范是定义在JVM实现中的 |
6 | 进入JVM实现,调用SystemDictionary的resolve_stream 方法,接受byte[],使用ClassFileParser进行解析 |
7 | SystemDictionary::define_instance_class |
8 | 如果类型被加载了,将类名、ClassLoader和类型的实例引用添加到SystemDictionary中 |
9 | 返回 |
10 | 返回 |
11 | 从Java实现返回到Java代码的defineClass,返回Class对象 |
12 | 返回给loadClass(Classname) 方法 |
13 | 返回给Java实现的SystemDictionary,因为在resolve_class中调用的ClassLoader.loadClass 。这里会做出一个判断,如果加载Class的ClassLoader并非传递给resolve_class的ClassLoader,那么会将类名、传递给resolve_class的ClassLoader以及类型的实例引用添加到SystemDictionary中 |
14 | 返回给Class.forName类型实例 |
上述的过程比较复杂,但是简化理解一下它所做的工作,我们将SystemDictionary记作缓存,Class.forName
或者说Java默认的类型加载过程是:
首先根据ClassLoader,我们称之为initialClassLoader和类名查找缓存,如果缓存有,则返回;
如果缓存没有,则调用ClassLoader.loadClass(类名)
,加载到类型后,保存<类名,真实加载类的ClassLoader,类型引用>到缓存,这里真实加载类的ClassLoader我们可以叫做defineClassLoader;
返回的类型在交给Java之前,将会判断defineClassLoader是否等于initialClassLoader,如果不等,则新增<类名,initialClassLoader,类型引用>到缓存。
这里区分initialClassLoader和defineClassLoader的原因在于,调用initialClassLoader的loadClass,可能最终委派给其他的ClassLoader进行了加载。
ClassLoader.loadClass(String className)
我们在分析了Class.forName之后,再看ClassLoader.loadClass()
就会变得简单很多,这个ClassLoader就是一个SCL,而ClassLoader.loadClass()
只是相当于一个简单的方法调用。
根据图2的所示,该过程开始于第4步,没有前3步,该过程简单说就是:调用ClassLoader.loadClass(类名)
,加载到类型后,保存<类名,真实加载类的ClassLoader,类型引用>到缓存,这里真实加载类的ClassLoader我们可以叫做defineClassLoader。也就是,调用ClassLoader.loadClass(类名)
之后,并不一定会在缓存中生成一条<类名,ClassLoader,类型引用>的记录,但是一定会生成一条<类名,真实加载类的ClassLoader,类型引用>的记录。
ClassLoader.findLoadedClass(String className)
该方法是protected final
修饰的方法,也就是ClassLoader的子类可以内部使用,但是无法通过ClassLoader.findLoadedClass
直接调用。
这个方法一直感觉很奇怪,从名称上看就是查询这个ClassLoader加载过的Class,如果加载过了,那么就返回类型实例。但是只看到获取,没有看到添加,又或者说它到底是从哪里获取的。
答案是从SystemDictionary中获取的,当调用ClassLoader.findLoadedClass(className)
时,会在SystemDictionary中以className和ClassLoader为key,进行查询,如果命中,则返回类型实例。