前言:之前看到一道面试题,解释jvm加载.class的原理。把笔者难住了,无奈搞开发也一段时间了,连基础的东西也没搞懂,实在感到羞愧。这几天有空都在看这方面的资料,也了解了个大概。就把自己看的这些资料在脑海里整理了一下,发一个心得。不敢说全部都是正确的,因为各种原因有些东西没办法寻根问底,但是估计也八九不离十了,有错误的地方还望各位指出啊。
- 加载过程
- 运行程序时,首先jre会找到jvm.dll(就是传说中的java虚拟机)。至于这个怎么找到虚拟机的过程,我这里就不做讨论了。
- 启动jvm后,首先会生成类加载器,系统提供的类加载器有3个:
- Bootstrap ClassLoader,这个类加载器加载基本的系统所需的类,主要加载的是(jre路径)/lib/rt.jar包。
- Extension ClassLoader,这个类加载器加载一些扩展的类,主要加载的是:(jre路径)/lib/ext文件夹下的包。
- AppClassLoader(又叫System ClassLoader,下面我都直接叫AppClassLoader吧),这个类主要加载的就是CLASSPATH下的包了,还有用户自定义的包,最重要的加载程序员写的程序。
4. 关于Bootstrap
所有的.class都是由类加载器进行加载。作为类加载器的起源,Bootstrap的作用非常重要,下面就来谈一下这个Bootstrap类加载器。
Bootstrap不是由java编写的,是由C++编写的,所以在java的逻辑上面,是没有Bootstrap这个类加载器。在程序中可以通过.getClassLoader来输出一个实体对象是由哪个加载器加载,但是如果是由Bootstrap加载的类,输出的时候会是null。请看一下下面的演示代码:
public class TestPrintClassLoader {
public static void main(String[] args) {
A a = new A();
/*输出自定义类A的加载器*/
System.out.println(a.getClass().getClassLoader());
/*cl为类加载器*/
ClassLoader cl = a.getClass().getClassLoader();
/*那么输出一下类加载器ClassLoader这个类又是由谁来加载*/
System.out.println(cl.getClass().getClassLoader());
}
}
class A{
/**自定义类A*/
}
代码中A类是程序员自己编写的,按上面所说类加载器的任务,A这个类应该是由AppClassLoader来加载的,而ClassLoader.class这个应该就是由Bootstrap ClassLoader加载的。
输出结果如下:
输出结果验证了这个说法。第二条输出结果为null,就是由Bootstrap ClassLoader加载的,大家可以查一下,ClassLoader.class是在java.lang包下面,也就是在rt.jar下面,启动之后是由BootstrapClassLoader加载。
有些朋友就纳闷了,输出是null而已,为什么一定就是Bootstrap ClassLoader加载?这个因为在java中所有的类都必须是类加载器加载,所以每一个类都有对应的类加载器。输出是null的话,对应就是Bootstrap了。
类加载器的阶层体系:
以下的内容多是看《java深度历险》的心得。
一切皆由Bootstrap开始,我们再回来讨论一下jvm加载的流程这个问题,从宏观来说,整个加载的流程是先加载好Bootstra后,再加载ExtClassLoader,然后再加载AppClassLoader,按顺序的加载必要的包和类,然后最后由AppClassLoader加载我们的程序,整个流程的效果类似于下图(该图取自《java深度历险》):
从开始执行运行.class的代码后的整体加载流程。由Bootstrap->ExtClassLoader->AppClassLoader,这个就是类加载的阶层关系。
先来看一下下面的代码:
public class TestPrintParent {
public static void main(String[] args) {
ClassLoader cl = TestPrintClassLoader.class.getClassLoader();
System.out.println(cl);
ClassLoader cl1 = cl.getParent();
System.out.println(cl1);
ClassLoader cl2 = cl1.getParent();
System.out.println(cl2);
}
}
输出的结果是:

显示的结果是AppClassLoader的Parent是ExtClassLoader,ExtClassLoader的Parent是Bootstrap。这样看起来好像是由Bootstrap加载ExtClassLoader,然后再由ExtClassLoader加载AppClassLoader,最后再由AppClassLoader加载我们自己写的类。如下图:
其实不然,AppCLassLoader并不是由ExtClassLoader加载的,都是由Bootstrap加载。我们由代码来解答吧:
public class TestPrintParent {
public static void main(String[] args) {
ClassLoader cl = TestPrintClassLoader.class.getClassLoader();
System.out.println(cl);
ClassLoader cl1 = cl.getParent();
System.out.println(cl1);
ClassLoader cl2 = cl1.getParent();
System.out.println(cl2);
System.out.println("-----------------");
/*AppClassLoader的加载器*/
System.out.println(cl.getClass().getClassLoader());
/*ExtClassLoader的加载器*/
System.out.println(cl1.getClass().getClassLoader());
}
}
以下是输出结果:
我们可以看到,AppClassLoader和ExtClassLoader都是由Bootstrap加载的,这就是为什么一切源于Bootstrap。
加载的过程其实是首先加载Bootstrap,Bootstrap除了做一些简单的加载初始化的工作之外,还将载入ExtClassLoader,并将Ext里面的Parent参数设定为null(说明其Parent是Bootstrap),然后还会载入
AppClassLoader,并将里面的Parent参数设定为ExtClassLoader实体(说明其Parent是ExtClassLoader)。
由此可见,类加载器由谁加载和Parent是谁没有任何关系,Parent的存在只是为了其他目的,其实就是为了双亲委派模式(下面会介绍什么是双亲委派模式)。所以这三个加载器正确的关系图应该是下面这图:
5. 双亲委派模式:
所谓的双清委派模式,其实就是当接到一个加载类的需求的时候,子类首先会申请让其父类来加载,以此递归,一直到达Bootstrap,如果父类没办法加载的话,才由子类去加载。
这样做有什么好处在,一方面防止重复加载,如果父类已经加载好的类,子类要求加载的时候,父类会直接返回该类的实体。另一方面没办法加载到父类已经成功加载的同名类,防止恶意篡改系统类。还有就是同一层的类加载器之间是不互通的,例如一个XXX.class类,由不同的AppClassLoader载入,它们之间是没办法通信的,这也有效地防止恶意或者误用别人的代码。