类加载过程
类从被加载到虚拟机内存开始,到卸载除内存,主要的生命周期包括:加载、验证、准备、解析、初始化、使用和卸载七个阶段。
它们开始的顺序如下图所示:
其中,加载、验证、准备、初始化这四个阶段是顺序开始的,解析阶段则不一定(为了支持Java的运行时绑定)
加载与类加载器
加载
加载阶段,虚拟机主要完成下面3个事情:
- 通过类的权限定名获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的
java.lang.Class
,作为方法区这个类各种数据的访问入口
加载阶段与连接阶段(验证、准备、解析)的部分内容是交叉进行的,加载阶段尚未完成,连接阶段可能已经开始,这两个阶段的开始时间仍然保持着固定的顺序。
类加载器
类加载器用于实现类的加载动作,两个不同的类加载器加载的同一个class文件属于两个不同的java.lang.Class
。
比较两个类是否相等,只有在两个类都由同一个类加载加载的前提下才有意义。
从JVM角度划分类加载器
- 启动类加载器(Bootstrap ClassLoader):使用C++实现,是JVM自身的一部分。(仅指HotSpot)
- 其他的类加载器:由Java实现,独立于JVM外部,继承自
java.lang.ClassLoader
从程序员角度划分类加载器
- 启动类加载器(Bootstrap ClassLoader):负责加载
\lib目录下的,或被-Xbootclasspath参数指定的路径中的,能被虚拟机所识别的类库到JVM内存中。 - 扩展类加载器(Extension ClassLoader):加载
\lib\ext目录下的,或者java.ext.dirs系统变量所指定的路径下的所有类库,开发者可以直接 *扩展类加载器*。 - 应用程序类加载器(Application ClassLoader):负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。
我们应用程序都是由这三种类加载器互相配合进行加载的,如有必要,还可以加入自定义的类加载器。加载器之间的关系如下图所示:
双亲委派模型
上图所示的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。
双亲委派模型要求除顶层的启动类加载器,其余类加载器都要有自己的父加载器。
双亲委派模型并不是一个强制性的约束模型,而是Java设计者推荐给开发者的一种类加载实现方式。
双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上。
因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
类加载与OSGI
OSGI是一个基于Java语言的动态模块化规范。Eclipse 就是基于 OSGi 技术来构建的。
OSGI中每个模块(称为bundle)类似与普通的java类库区别不大,两者都以JAR格式封装,并且内部存储的都是Java Package和Class。
OSGi 中的每个模块(bundle)都包含 Java 包和类。模块可以声明它所依赖的需要导入(import)的其它模块的 Java 包和类(通过 Import-Package),也可以声明导出(export)自己的包和类,供其它模块使用(通过 Export-Package)。
也就是说需要能够隐藏和共享一个模块中的某些 Java 包和类。这是通过 OSGi 特有的类加载器机制来实现的。
OSGi 中的每个模块都有对应的一个类加载器。它负责加载模块自己包含的 Java 包和类。当它需要加载 Java 核心库的类时(以 java开头的包和类),它会代理给父类加载器(通常是启动类加载器)来完成。当它需要加载所导入的 Java 类时,它会代理给导出此 Java 类的模块来完成加载。模块也可以显式的声明某些 Java 包和类,必须由父类加载器来加载。只需要设置系统属性 org.osgi.framework.bootdelegation
的值即可。
Java和J2EE的类加载模型都是层次化的,只能委托给上一层类加载器;(双亲委派模型)
而OSGi类加载模型则是网络图状的,可以在bundle间互相委托。——这样更合理,因为bundle间的依赖关系并不是层次化的。