类装载器负责把类装入 Java 虚拟机(JVM)。简单的应用程序可以用 Java 平台内置的类装载工具装载类;更复杂的应用程序则倾向于定义自己定制的类装载器。但是,不论使用哪种类装载器,在类装载过程中都可能发生许多问题。如果想避免这类问题,需要理解类装载的基本机制。
类装载器委托模型
注意:在这个继承体系中,父类装载器始终具有优先装载的权力。比如子类接收到需要装载Person类的请求,它会先委托给父类去尝试装载,父类装载失败,子类才会接着尝试装载。但是,都被规定了各自的装载空间。[例如:“System Class Loader” 不会到/jre/lib/* 下去load]
引导类装载器(也称作基本(primordial) 类装载器):
它本身是C++写的程序。可以独立运行,它是JVM的运行起点。不能由 Java 代码实例化。(通常是因为它是作为 VM 本身的一部分实现的。)这个类装载器可以从启动的类路径装载核心系统类,通常是位于 jre/lib 目录的 JAR 文件。
扩展(extension) 类装载器(也称作标准扩展 类装载器):
是引导类装载器的一个孩子。它的主要职责是从扩展目录装载类,通常位于 jre/lib/ext 目录。这提供了简单地访问新扩展的能力,例如不同的安全扩展,不需要修改用户的类路径即可实现。
系统(system) 类装载器(也称作应用程序 类装载器):
负责从 CLASSPATH 环境变量指定的路径装载代码。默认情况下,这个类装载器是用户创建的任何类装载器的父类。这也是 ClassLoader.getSystemClassLoader() 方法返回的类装载器。
类装载的阶段
一个类的运行,JVM做会以下几件事情: 1、类装载 2、链接 3、初始化 4、实例化
一个类的装载可分成三个阶段:类装载、链接和初始化
类装载的 阶段示意图
装载 阶段包括:找到必要的类(通过查找每个类路径)并装载字节码。在 JVM 中,装载阶段为类对象提供了非常基本的内存结构。在这一阶段不处理方法、字段和引用的其他类。所以,类还不能使用。
链接 是三个阶段中最复杂的一个。可以把它分成三个主要阶段:
- 字节码验证。 类装载器对于类的字节码要做许多检测,以确保格式正确、行为正确。
- 类准备。 这个阶段准备代表每个类中定义的字段、方法和实现接口所必需的数据结构。
- 解析。 这个阶段,类装载器装载类所引用的其他所有类。可以用许多方式引用类:
在初始化 阶段,类中包含的静态初始化器都被执行。在这一阶段末尾,静态字段被初始化成默认值。
在这三个阶段末尾,类被完整地装载,可以使用了。请注意可以用惰性方式执行类装载,所以类装载过程的某些部分可能在第一次使用类的时候[比如:初始化]才执行,而不是在装载时执行。
举例说明:
Class.forName(args[0], false,off.getClass().getClassLoader());
告诉JVM不需在load class之后进行initial的工作。这样,将initial的工作推迟到了newInstance的时候进行。所以,static块的绝对不是“只在类被第一次实例化的时候才会被仅仅调用一次”,而应
该是在类被初始化的时候,仅仅调用一次
Class.forName(args[0], true,off.getClass().getClassLoader());
告诉JVM在load class之后立即进行initial的工作
显式装载与隐式装载
类装载的方式有两种 —— 显式 或 隐式,两者之间有些细微差异。
显式 类装载发生在使用以下方法调用装载的类的时候:
- cl.loadClass()(cl 是 java.lang.ClassLoader 的实例)
- Class.forName()(启动的类装载器是当前类定义的类装载器)
当调用其中一个方法的时候,指定的类(以类名为参数)由类装载器装载。如果类已经装载,那么只是返回一个引用;否则,装载器会通过委托模型装载类。
隐式 类装载发生在由于引用、实例化或继承导致装载类的时候(不是通过显式方法调用)。在每种情况下,装载都是在幕后启动的,JVM 会解析必要的引用并装载类。与显式类装载一样,如果类已经装载了,那么只是返回一个引用;否则,装载器会通过委托模型装载类。例如:Person p = new Person();
类的装载通常组合了显式和隐式类装载。例如,类装载器可能先显式地装载一个类,然后再隐式地装载它引用的所有类。
参考:IBM技术文档
Goingmm 2006-2-23