在上一节中讲到突破双亲委派模型的CurrentClassLoader和ContextClassLoader,接下来重点讲Class.forName和ClassLoader.loadClass ,众所周知它们都可以用来加载目标类,它们之间有一个小小的区别,那就是 Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass 则会报错。
Class> x = Class.forName("[I");
System.out.println(x); //输出:class [I
x = ClassLoader.getSystemClassLoader().loadClass("[I");
System.out.println(x);//输出:Exception in thread "main" java.lang.ClassNotFoundException: [I
but no more than that?
Class.forName是根据给定的类型全名从CurrentClassLoader中加载指定的类型,CurrentClassLoader的加载过程是由JVM运行时来控制的,是无法通过Java编程来更改的。ClassLoader.loadClass 只是相当于一个简单的方法调用。
public final class Class implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement {
@CallerSensitive
public static Class> forName(String className)
throws ClassNotFoundException {
Class> caller = Reflection.getCallerClass();//获取到调用Class.forName方法所在的类
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
/** Called after security check for system loader access checks have been made. */
private static native Class> forName0(String name, boolean initialize,
ClassLoader loader,
Class> caller)
throws ClassNotFoundException;
}
加载的逻辑在native方法forName0
中定义,也就是forName进行的类加载行为已经脱离了Java代码的控制范围,进入到了Java运行时环境把控的阶段。
//Class.c
JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname, jboolean initialize, jobject loader)
{
// 用JVM_FindClassFromClassLoader这个函数来进行类型加载
cls = JVM_FindClassFromClassLoader(env, clname, initialize,
loader, JNI_FALSE);
done:
if (clname != buf) {
free(clname);
}
return cls;
}
//jvm.cpp
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加载类型的数据结构。它是以类的全限定名再加上类加载器作为key,进而确定Class引用。
类型加载时,以ClassLoader和需要加载的类型全限定名作为参数在SystemDictionary中进行查询,如果能够查询到则返回。如果无法找到,则调用loader.loadClass(className)进行加载,这一步将进入到Java代码中。
对于loadClass而言,基本等同于loader.defineClass(loader.getResource(file).getBytes())
,它做了两件事:
一个类加载的过程,在运行时Java(JVM)和java代码之间来回切换,有点复杂,经过简化后的Class.forName加载过程,红色的框代表是Java代码,或者说能够在rt.jar中找到的内容,绿色的框代表是JVM的实现代码,是由C和C++实现的,当由Java调用C的代码时,使用绿色的箭头,反之使用红色箭头。
关键的步骤:简化理解一下它所做的工作,我们将SystemDictionary记作缓存,Class.forName
或者说Java默认的类型加载过程是:首先根据ClassLoader,我们称之为initialClassLoader和类名查找缓存,如果缓存有,则返回,如果缓存没有,则调用ClassLoader.loadClass(类名)。
步骤 | 说明 |
---|---|
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中调用的 |
14 | 返回给Class.forName类型实例 |
我们在分析了Class.forName之后,再看ClassLoader.loadClass()
就会变得简单很多,而ClassLoader.loadClass()
只是相当于一个简单的方法调用。
当调用ClassLoader.findLoadedClass(className)
时,会在SystemDictionary中以className和ClassLoader为key,进行查询,如果命中,则返回类型实例。