深入探讨 Java 类加载器
类加载器(class loader)是 Java™中的一个很重要的概念。类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。本文首先详细介绍了 Java 类加载器的基本概念,包括代理模式、加载类的具体过程和线程上下文类加载器等,接着介绍如何开发自己的类加载器,最后介绍了类加载器在 Web 容器和 OSGi™中的应用。
类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一。它使得 Java 类可以被动态加载到 Java
虚拟机中并执行。类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的。Java
Applet 需要从远程下载 Java 类文件到浏览器中并执行。现在类加载器在 Web 容器和 OSGi
中得到了广泛的使用。一般来说,Java 应用的开发人员不需要直接同类加载器进行交互。Java
虚拟机默认的行为就已经足够满足大多数情况的需求了。不过如果遇到了需要与类加载器进行交互的情况,而对类加载器的机制又不是很了解的话,就很容易花大量
的时间去调试 ClassNotFoundException和
NoClassDefFoundError等异常。本文将详细介绍 Java 的类加载器,帮助读者深刻理解
Java 语言中的这个重要概念。下面首先介绍一些相关的基本概念。
类加载器基本概念
顾 名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中。一般来说,Java 虚拟机使用
Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java
字节代码(.class 文件)。类加载器负责读取 Java 字节代码,并转换成
java.lang.Class类的一个实例。每个这样的实例用来表示一个 Java 类。通过此实例的
newInstance()方法就可以创建出该类的一个对象。实际的情况可能更加复杂,比如 Java
字节代码可能是通过工具动态生成的,也可能是通过网络下载的。
基本上所有的类加载器都是 java.lang.ClassLoader类的一个实例。下面详细介绍这个
Java 类。
java.lang.ClassLoader类介绍
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个
Java 类,即
java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载
Java
应用所需的资源,如图像文件和配置文件等。不过本文只讨论其加载类的功能。为了完成加载类的这个职责,ClassLoader提供了一系列的方法,比较重要的方法如
表 1所示。关于这些方法的细节会在下面进行介绍。
表 1. ClassLoader 中与加载类相关的方法
方法
说明
getParent()
返回该类加载器的父类加载器。
loadClass(String name)
加载名称为
name的类,返回的结果是 java.lang.Class类的实例。
findClass(String name)
查找名称为 name的类,返回的结果是 java.lang.Class类的实例。
findLoadedClass(String name)
查找名称为 name的已经被加载过的类,返回的结果是
java.lang.Class类的实例。
defineClass(String name, byte[] b, int off, int
len) 把字节数组
b中的内容转换成 Java 类,返回的结果是 java.lang.Class类的实例。这个方法被声明为
final的。
resolveClass(Class<问号>
a) 链接指定的 Java
类
对于
表 1中给出的方法,表示类名称的 name参数的值是类的二进制名称。需要注意的是内部类的表示,如
com.example.Sample$1和
com.example.Sample$Inner等表示方式。这些方法会在下面介绍类加载器的工作机制时,做进一步的说明。下面介绍类加载器的树状组织结构。
类加载器的树状组织结构
Java 中的类加载器大致可以分成两类,一类是系统提供的,另外一类则是由 Java 应用开发人员编写的。系统提供的类加载器主要有下面三个:
- 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自
java.lang.ClassLoader。 - 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
- 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java
类。一般来说,Java 应用的类都是由它来完成加载的。可以通过
ClassLoader.getSystemClassLoader()来获取它。
除了系统提供的类加载器以外,开发人员可以通过继承
java.lang.ClassLoader类的方式实现自己的类加载器,以满足一些特殊的需求。
除了引导类加载器之外,所有的类加载器都有一个父类加载器。通过
表 1中给出的 getParent()方
法可以得到。对于系统提供的类加载器来说,系统类加载器的父类加载器是扩展类加载器,而扩展类加载器的父类加载器是引导类加载器;对于开发人员编写的类加
载器来说,其父类加载器是加载此类加载器 Java 类的类加载器。因为类加载器 Java 类如同其它的 Java
类一样,也是要由类加载器来加载的。一般来说,开发人员编写的类加载器的父类加载器是系统类加载器。类加载器通过这种方式组织起来,形成树状结构。树的根
节点就是引导类加载器。图
1中给出了一个典型的类加载器树状组织结构示意图,其中的箭头指向的是父类加载器。
图 1. 类加载器树状组织结构示意图
代码清单 1演示了类加载器的树状组织结构。
清单 1. 演示类加载器的树状组织结构
public class ClassLoaderTree {
public static void main(String[] args) {
ClassLoader loader = ClassLoaderTree.class.getClassLoader();
while (loader != null) {
System.out.println(loader.toString());
loader = loader.getParent();
}
}
}
每个 Java 类都维护着一个指向定义它的类加载器的引用,通过
getClassLoader()方法就可以获取到此引用。代码清单
1中通过递归调用 getParent()方法来输出全部的父类加载器。代码清单
1的运行结果如
代码清单 2所示。
清单 2. 演示类加载器的树状组织结构的运行结果
sun.misc.Launcher$AppClassLoader@9304b1 sun.misc.Launcher$ExtClassLoader@190d11
如
代码清单 2所示,第一个输出的是 ClassLoaderTree类的类加载器,即系统类加载器。它是
sun.misc.Launcher$AppClassLoader类的实例;第二个输出的是扩展类加载器,是
sun.misc.Launcher$ExtClassLoader类的实例。需要注意的是这里并没有输出引导类加载器,这是由于有些
JDK 的实现对于父类加载器是引导类加载器的情况,getParent()方法返回
null。
在了解了类加载器的树状组织结构之后,下面介绍类加载器的代理模式。
类加载器的代理模式
类
加载器在尝试自己去查找某个类的字节代码并定义它时,会先代理给其父类加载器,由父类加载器先去尝试加载这个类,依次类推。在介绍代理模式背后的动机之
前,首先需要说明一下 Java 虚拟机是如何判定两个 Java 类是相同的。Java
虚拟机不仅要看类的全名是否相同,还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,被不同的类加
载器加载之后所得到的类,也是不同的。比如一个 Java 类
com.example.Sample,编译之后生成了字节代码文件
Sample.class。两个不同的类加载器 ClassLoaderA和
ClassLoaderB分别读取了这个 Sample.class文件,并定义出两个
java.lang.Class类的实例来表示这个类。这两个实例是不相同的。对于 Java
虚拟机来说,它们是不同的类。试图对这两个类的对象进行相互赋值,会抛出运行时异常
ClassCastException。下面通过示例来具体说明。代码清单
3中给出了 Java 类 com.example.Sample。
清单 3. com.example.Sample 类
package com.example;
public class Sample {
private Sample instance;
public void setSample(Object instance) {
this.instance = (Sample) instance;
}
}
如
代码清单 3所示,com.example.Sample类的方法
setSample接受一个
java.lang.Object类型的参数,并且会把该参数强制转换成
com.example.Sample类型。测试 Java 类是否相同的代码如
代码清单 4所示。
清单 4. 测试 Java 类是否相同
public void testClassIdentity()
{
String classDataRootPath = "C:\\workspace\\Classloader\\classData";
FileSystemClassLoader fscl1 = new FileSystemClassLoader(classDataRootPath);
FileSystemClassLoader fscl2 = new FileSystemClassLoader(classDataRootPath); String className = "com.example.Sample";
try { Class
1.类的加载过程
JVM将类加载过程分为三个步骤:装载(Load),链接(Link)和初始化(Initialize)链接又分为三个步骤,如下图所示:

1) 装载:查找并加载类的二进制数据;
2)链接:
验证:确保被加载类的正确性;
准备:为类的静态变量分配内存,并将其初始化为默认值;
解析:把类中的符号引用转换为直接引用;
3)初始化:为类的静态变量赋予正确的初始值;
2. 类的初始化
1)创建类的实例,也就是new一个对象
2)访问某个类或接口的静态变量,或者对该静态变量赋值
3)调用类的静态方法
4)反射(Class.forName("com.lyj.load"))
5)初始化一个类的子类(会首先初始化子类的父类)
6)JVM启动时标明的启动类,即文件名和类名相同的那个类
3.类的加载


加载类的方式有以下几种:
1)从本地系统直接加载 2)通过网络下载.class文件
3)从zip,jar等归档文件中加载.class文件
4)从专有数据库中提取.class文件
5)将Java源文件动态编译为.class文件(服务器)
4.加载器
来自http://blog.csdn.net/cutesource/article/details/5904501
JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

1)Bootstrap ClassLoader
负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类
2)Extension ClassLoader
负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
3)App ClassLoader
负责记载classpath中指定的jar包及目录中class
4)Custom ClassLoader
属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序 是自顶向下,也就是由上层来逐层尝试加载此类。
浙公网安备 33010602011771号