本帖最后由 付旭 于 2012-1-1 10:48 编辑
当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件。那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做【加载 】。加载完成之后,我们就可以进行一系列的运行前准备工作了,比如: 为类静态变量开辟空间,将常量池存放在方法区内存中并实现常量池地址解析,初始化类静态变量等等。这篇文章我们要好好谈谈JVM是如何加载class文件的?
1、JVM加载类的过程
当我们使用命令来执行某一个Java程序(比如Test.class)的时候:java Test
(1) java.exe 会帮助我们找到 JRE ,接着找到位于 JRE 内部的 jvm.dll ,这才是真正的 Java 虚拟机器 , 最后加载动态库,激活 Java 虚拟机器。
(2) 虚拟机器激活以后,会先做一些初始化的动作,比如说读取系统参数等。一旦初始化动作完成之后,就会产生第一个类装载器 ―― Bootstrap Loader(启动类装载器 ) 。
(3) Bootstrap Loader 所做的初始工作中,除了一些基本的初始化动作之外,最重要的就是加载 Launcher.java 之中的 ExtClassLoader(扩展类装载器) ,并设定其 Parent 为 null ,代表其父加载器为 BootstrapLoader 。
(4) 然后 Bootstrap Loader 再要求加载 Launcher.java 之中的 AppClassLoader(用户自定义类装载器 ) ,并设定其 Parent 为之前产生的 ExtClassLoader 实体。这两个加载器都是以静态类的形式存在的。
这里要请大家注意的是, Launcher$ExtClassLoader.class 与 Launcher$AppClassLoader.class 都是由 Bootstrap Loader 所加载,所以 Parent 和由哪个类加载器加载没有关系。
初学者对这个过程很难理解,我们将在下面详细的讲讲类装载器和"Parent"是什么。
2、类装载器体系结构
JVM加载class文件必须通过一个叫做类装载器的程序,它的作用就是从磁盘文件中将要运行代码的字节码流加载进内存(JVM管理的方法区)中。下面是几个比较重要的概念:
(1) 启动类装载器 : 每个Java虚拟机实现都必须有一个启动类装载器。它只负责在系统类(核心Java API的class文件)的安装路径中查找要装入的类。这个装载器的实现由C++ 所撰写而成,是JVM实现的一部分。
(2) 扩展类装载器和自定义类装载器 : 负责除核心Java API以外的其它class文件的装载。例如、用于安装或下载标准扩展的class文件,在类路径中发现的类库的class文件,用于应用程序运行的class文件等等。这里有一点需要注意:自定义类装载器并非由应用程序员自己实现,它也是JVM
(3) 命名空间: Java虚拟机为每一个类装载器维护一个唯一标识的命名空间。一个Java程序可以多次装载具有同一个全限定名的多个类。 Java虚拟机要确定这"多个类"的唯一性,因此,当多个类 装载器都装载了同名的类时,为了唯一地标识这个类,还要在类名前加上装载该类的类装载器的标识(指出了类所位于的命名空间)。下图显示了两个类装载器有关的命名空间,显然,不同的类装载器允许装载相同的类Volcano。
命名空间有助于安全的实现,因为你可以有效地在装入了不同命名空间的类之间设置一个防护罩。在Java虚拟机中,在同一个命名空间内的类可以直接进行交 互,而不同的命名空间中的类甚至不能察觉彼此的存在,除非显式地提供了允许它们进行交互的机制。一旦加载后,如果一个恶意的类被赋予权限访问其他虚拟机加 载的当前类,它就可以潜在地知道一些它不应该知道的信息,或者干扰程序的正常运行。
3、双亲委托模型
用户自定义类装载器经常依赖其他类装载器——至少依赖于虚拟机启动时创建的启动类装载器—来帮助它实现一些类装载请求:.在版本1.2前,非启动类装载器 必须显式地求助于其他类装载器,类装载器可以请求另一个用户自定义的类装载器来装载一个类,这个请求是通过对被请求的用户自定义类装载器调用 loadClass()来实现的。除此以外,类装载器也可以通过调用findSystemClass()来请求启动类装载器来装载类,这是类 ClassLoader中的一个静态方法。 在版本1.2中,类装载器请求另一个类装载器来装载类型的过程被形式化,称为双亲委派模式 。从版本1.2开始、除启动类装载器以外的每一个类装载器,都有一个“双亲”类装载器 ,在某个特定的类装载器试图以常用方式装载类型以前,它会先默认地将这个任务“委派”给它的双亲——清求它的双亲来装载这个类型。这个双亲再依次请求它自 己的双亲来装载这个类型。这个委派的过程一直向上继续,直到达到启动类装载器,通常启动类装载器是委派链中的最后一个类装载器。如果一个类装载器的双亲类 装载器有能力来装载这个类型。则这个类装载器返回这个类型。否则,这个类装载器试图自己来装载这个类。 当Java虚拟机开始运行时,在应用程序开始启动以前,它至少创建一个用户自定义装载器,也可能创建多个.所有这些装载器被连接在一个Parent-Child的委托链中,在这个链的顶端是启动类装载器。例如:假设你写了一个应用程序,在虚拟机上运行它.虚拟机在启动时实例化了两个用户自定义类装载器:一个"扩展类装载器",一个"类路径类装载器".这些类装载器和启动类装载器一起联入一个Parent-Child委托链中,如下图所示. 上图所示类路径类装载器的Parent是扩展类装载器, 扩展类装载器的Parent是启动类装载器.在图2中,类路径类装载器就被实例为系统类装载器.假设你的程序实例化它的网络类装载器,它就指明了系统类装载器作为它的Parent.
下面的例程说明了类装载器的父子关系.
Java代码
1. package test;
2. import java.net.URL;
3. import java.net.URLClassLoader;
4. public class ClassLoaderTest {
5. private static int count = -1;
6. public static void testClassLoader(Object obj) {
7. if (count < 0 && obj == null) {
8. System.out.println("Input object is NULL";
9. return;
10. }
11. ClassLoader cl = null;
12. if (obj != null && !(obj instanceof ClassLoader)) {
13. cl = obj.getClass().getClassLoader();
14. } else if (obj != null) {
15. cl = (ClassLoader) obj;
16. }
17. count++;
18. String parent = "";
19. for (int i = 0; i < count; i++) {
20. parent += "Parent ";
21. }
22. if (cl != null) {
23. System.out.println(
24. parent + "ClassLoader name = " + cl.getClass().getName());
25. testClassLoader(cl.getParent());
26. } else {
27. System.out.println(
28. parent + "ClassLoader name = BootstrapClassLoader";
29. count = -1;
30. }
31. }
32. public static void main(String[] args) {
33. URL[] urls = new URL[1];
34. URLClassLoader urlLoader = new URLClassLoader(urls);
35. ClassLoaderTest.testClassLoader(urlLoader);
36. }
37.}
package test;
import java.net.URL;
import java.net.URLClassLoader;
public class ClassLoaderTest {
private static int count = -1;
public static void testClassLoader(Object obj) {
if (count < 0 && obj == null) {
System.out.println("Input object is NULL";
return;
}
ClassLoader cl = null;
if (obj != null && !(obj instanceof ClassLoader)) {
cl = obj.getClass().getClassLoader();
} else if (obj != null) {
cl = (ClassLoader) obj;
}
count++;
String parent = "";
for (int i = 0; i < count; i++) {
parent += "Parent ";
}
if (cl != null) {
System.out.println(
parent + "ClassLoader name = " + cl.getClass().getName());
testClassLoader(cl.getParent());
} else {
System.out.println(
parent + "ClassLoader name = BootstrapClassLoader";
count = -1;
}
}
public static void main(String[] args) {
URL[] urls = new URL[1];
URLClassLoader urlLoader = new URLClassLoader(urls);
ClassLoaderTest.testClassLoader(urlLoader);
}
}
以上例程的输出为:
ClassLoader name = java.net.URLClassLoader
Parent ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent Parent ClassLoader name = BootstrapClassLoader
类装载器请求过程
以上例程1为例.将main方法改为:
ClassLoaderTest tc = new ClassLoaderTest();
ClassLoaderTest.testClassLoader(tc);
输出为:
ClassLoader name = sun.misc.Launcher$AppClassLoader
Parent ClassLoader name = sun.misc.Launcher$ExtClassLoader
Parent Parent ClassLoader name = BootstrapClassLoader
|