深入理解Java虚拟机 - 类加载器
引子:
类加载器(classloader)是独立于虚拟机之外,可以独立实现的代码模块。
OSGi使用了类加载器的这一特点实现其热插拔的特性
Java同C++等语言不通,其连接操作不是在编译期间进行的,而是在程序运行期间进行的,这也就为java的动态扩展的语言特性提供了基础。
首先介绍下虚拟机的类加载机制:
类加载的时机:
类被从加载到虚拟机内存开始,到卸出内存为止,整个生命周期包括了以下七个过程:
加载 - 验证 - 准备 - 解析 - 初始化 - 使用 - 卸载
加载:
1. 通过一个类的全限定名获取定义此类的二进制流(决定了这个类的二进制流可以从zip包、jar包、计算时生成、网络甚至数据库中读取)
2. 将字节流代表的静态存储结构转化为方法区的运行时数据结构
3. 在java堆中生成一个代表该类的class对象
验证:
首先java编译器会对代码进行验证,不合要求的指令不会通过编译,但虚拟机因执行字节码,也需要对字节码进行验证。
不同的虚拟机实现对验证有着不同的定义
准备、解析、初始化
下面是类加载器:
类加载器在Java虚拟机外部去实现,以便让应用程序自己去获取所需要的类。
类加载器虽然只用于实现类的加载动作,但都需要类加载器和类本身来共同确认两个类是否相同,也就是说如果同一个class类被不同的类加载器加载,这两个类仍然是不同的
双亲委派模型:
从java虚拟机的角度来看,只有两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),该加载器由C++实现,是虚拟机的一部分
另外一种就是其他的类加载器,这些加载器由Java实现,独立于虚拟机外并且全部继承自抽象类java.lang.ClassLoader
如果更为细分,可以分为如下三种:
该类负责加载存放在<JAVA_HOME>\lib目录中的,或者被-Xbootclasspath参数制定的,并且是虚拟机识别(文件名唯一识别,否则不加载,即使放到了对应目录下)的类库加载到虚拟机内存中去,该启动类无法被Java程序直接引用。
该加载器由sun.misc.Launcher$ExtClassLoader实现,负责加载<JAVA_HOME>\lib\ext目录中的,或者被java.ext.dirs系统变量所指定的类库,可以直接使用
由sun.misc.Lanucher$AppClassLoader实现,该加载器是ClassLoader中的getSystemClassLoader()方法的返回值,所以一般也称系统类加载器。负责加载用户路径(-classpath)上的类库,开发者可以直接使用该加载器,如果没有显示定义,一般情况下使用的就是该类加载器
下图为类加载器的双亲委派模型:
双亲委派模型除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类实现,该实现不是通过继承来实现的,而是通过组合来实现的,通过这样的层次关系,实现了类加载的优先级。
破坏双亲委派模型:
双亲委派模型并非强制性约束,而是java设计者推荐的类加载器实现模型。破坏双亲委派模型的两个典型是:
JDNI和OSGi
OSGi实现其热部署的关键是其自定义类加载器的实现,每个程序模块(Bundle)都有自己的一个实现,当需要替换bundle时,会将类加载器一同替换以实现热替换。OSGi中的类加载器已经不再具有双亲委派这种具有典型层次结构特征了,而是更加类似于网状结构。