java.lang.Class.forName(…)
和java.lang.ClassNotFoundExcetpion
是我们经常遇到的, 藏在他们身后的,是Java的类加载器。
JVM预定义的三种类加载器有:启动(Bootstrap)类加载器、标准扩展(Extension)类加载器和系统(System)类加载器,当一个JVM启动的时候,Java缺省开始使用这些类装入器。
除了以上三种类加载器,还有一种比较特殊的类型就是线程上下文类加载器,这个暂不介绍。
启动类加载器是通过java.lang.ClassLoader
中的private native final Class findLoadedClass0(String name);
这一本地方法实现的类装入器,它负责将<Java_Runtime_Home>/lib
下面的类库加载到内存中。
扩展类加载器是由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)
实现,它负责将<Java_Runtime_Home >/lib/ext
或者由系统变量 java.ext.dir
指定位置中的类库加载到内存中。
系统类加载器是由Sun的AppClassLoader(sun.misc.Launcher$AppClassLoader)
实现,它负责将系统类路径CLASSPATH
中指定的类库加载到内存中。
上面三种类型的类加载器中,启动(Bootstrap)类加载器是通过本地方法实现的,无法直接获取到启动类加载器的引用;而标准扩展(Extension)类加载器和系统(System)类加载器均为sun.misc.Launcher
的内部类,分别对应ExtClassLoader
和AppClassLoader
,这两个内部类均继承自URLClassLoader
,其继承关系为:
java.net.URLClassLoader -> java.security.SecureClassLoader -> java.lang.ClassLoader
所以ExtClassLoader
和AppClassLoader
这两个内部类间接继承自ClassLoader
JVM在加载类时默认采用的是双亲委派机制,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给双亲加载器,依次递归,如果双亲加载器可以完成类加载任务,就成功返回;只有双亲类加载器无法完成此加载任务时,才自己去加载。
注意这里的双亲并不是继承上的关系,而是指的ClassLoader
中的一个字段。在源码中更容易描述双亲委派机制:
//java.lang.ClassLoader.java
// 双亲的定义
private final ClassLoader parent;
// 加载方法
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
//如果没有加载过,则进行加载
if (c == null) {
long t0 = System.nanoTime();
try {
// 先检查双亲
if (parent != null) { // 如果有双亲,则委派给双亲去加载
c = parent.loadClass(name, false);
} else { // 没有双亲才使用Bootstrap类加载器加载
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
// 如果父类加载器和启动类加载器都不能完成加载任务,才调用自身的加载功能
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
从源码中可以看出,双亲为null
时才会调用Bootstrap
启动类加载器,所以启动类加载器可以当成是最后一个双亲。
ExtClassLoader
和AppClassLoader
这两个内部类继承自URLClassLoader
,而URLClassLoader
和SecureClassLoader
中仅做了一些检查,双亲委派机制在各个类加载器中是一直有效的。
另外parent
被private final
修饰,并且定义的时候没有赋初值,这就必须在构造方法中进行赋值。
我们从源码中分析下各个类加载器的parent
到底被赋值为什么。
// java.loang.ClassLoader.java
// 三个构造方法
protected ClassLoader() {
this(checkCreateClassLoader(), getSystemClassLoader());
}
protected ClassLoader(ClassLoader parent) {
this(checkCreateClassLoader(), parent);
}
private ClassLoader(Void unused, ClassLoader parent) {
this.parent = parent;
...
}
// 不指定parent时,默认的parent为系统(System)类加载器,即AppClassLoader
public static ClassLoader getSystemClassLoader() {
// 通过java.lang.ClassLoader.getSystemClassLoader()可以直接获取到系统类加载器,后面会通过代码进行测试
...
}
==========================================================================================================
// sun.misc.Launcher.java
private ClassLoader loader;
// Launcher 的构造方法
public Launcher() {
// Create the extension class loader
ClassLoader extcl; // 这里定义了 extcl
try {
extcl = ExtClassLoader.getExtClassLoader(); // 调用了内部类ExtClassLoader的方法,生成了ExtClassLoader
} catch (IOException e) {
throw new InternalError("Could not create extension class loader");
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl); // 调用了内部类的getAppClassLoader方法
} catch (IOException e) {
throw new InternalError("Could not create application class loader");
}
...
}
// 内部类 AppClassLoader
static class AppClassLoader extends URLClassLoader {
// AppClassLoader 的构造方法,所以生成AppClassLoader时必须指定parent
AppClassLoader(URL[] urls, ClassLoader parent) {
// 在此设置了 AppClassLoader 的 parent
super(urls, parent, factory);
}
// 内部类的方法
public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
return AccessController.doPrivileged( new PrivilegedAction<AppClassLoader>() {
public AppClassLoader run() {
URL[] urls = (s == null) ? new URL[0] : pathToURLs(path);
// 这里指定了 AppClassLoader 的parent为extcl
return new AppClassLoader(urls, extcl); // 生成了AppClassLoader并且设置其parent为extcl
}
});
}
}
// 内部类 ExtClassLoader
static class ExtClassLoader extends URLClassLoader {
// ExtClassLoader 的构造方法
public ExtClassLoader(File[] dirs) throws IOException {
// 调用URLClassLoader的构造方法 URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
// 直接把 ExtClassLoader 的 parent 设置为null
super(getExtURLs(dirs), null, factory);
}
}
通过以上的源码分析可知,在sun.misc.Launcher.java
中做了如下工作
loader
设置为AppClassLoader
AppClassLoader
的parent
设置为ExtClassLoader
ExtClassLoader
的parent
设置为null
程序源码:
public class TestClassLoader {
public static void main(String[] args) {
try {
System.out.println(ClassLoader.getSystemClassLoader());
System.out.println(ClassLoader.getSystemClassLoader().getParent());
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果为:
sun.misc.Launcher$AppClassLoader@30a4effe
sun.misc.Launcher$ExtClassLoader@1c8825a5
null
这说明AppClassLoader
的双亲为ExtClassLoader
,ExtClassLoader
的双亲为null
,这跟分析源码得出的结论一致。
public class TestClassLoader {
public static void main(String[] args) {
try {
// 通过Class.forName加载类
Class loadedClass = Class.forName("com.test.classloader.TestClassLoader");
// 查看被加载的类是被那个类加载器加载的
System.out.println(loadedClass.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出结果为:
sun.misc.Launcher$AppClassLoader@30a4effe
这说明系统默认的类加载器为AppClassLoader
。
将上面编译好的class文件打包成classloader.jar
(最好使用集成工具进行打包),将classloader.jar
放到<Java_Runtime_Home >/lib/ext
目录下,再次运行,结果成了
sun.misc.Launcher$ExtClassLoader@2e5f8245
这说明通过双亲委派机制,ExtClassLoader
抢先加载了类
将classloader.jar
拷贝一份到<Java_Runtime_Home >/lib
目录下,再次运行,结果仍为sun.misc.Launcher$ExtClassLoader@2e5f8245
这说明放到<Java_Runtime_Home >/lib
目录下的文件并没有被加载,这跟双亲委派机制并不矛盾。
虚拟机出于安全等因素考虑,不会加载<Java_Runtime_Home>/lib
存在的陌生类,开发者通过将要加载的非JDK自身的类放置到此目录下期待启动类加载器加载是不可能的。
做个进一步验证,删除<Java_Runtime_Home>/lib/ext
目录下的classloader.jar
和工程输出目录下对应的class文件,然后再运行测试代码,则将会有ClassNotFoundException
异常抛出。
ClassLoader
的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间, 所以不同类加载器加载的相同类,其类型也是不一样的。loadClass(...)
方法。<Java_Runtime_Home>/lib
下的类, 但不能加载<Java_Runtime_Home>/lib/ext
目录下的类。