JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化。
加载:
加载是类加载过程的一个阶段,这个阶段在内存中生成一个代表类的java.lang.Class对象,作为方法区这个类的各种数据的入口,注意这里不一定非得要从一个Class文件获取,这里既可以从zip包中读取,也可以在运行时计算生成,也可以由其他文件生成。
验证:
确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,并且不会损害虚拟机的安全。
准备:
正式为类变量分配内存,并设置类变量的初始值阶段,在方法区中分配这些变量使用的内存空间。实际上变量在编译前的初始值为0,编译后会生成对应熟悉,在准备阶段虚拟机会根据属性进行赋值。
解析:
解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。
符号引用和直接引用的区别:
符号引用是与虚拟机的布局无关,引用的目标不一定已经加载到内存中,各种虚拟机实现的的内存布局不同,但是它们能够接收的符号引用是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
直接引用是指向目标的指针,相对偏移量是一个能够间接定位目标的句柄,如果有直接引用,引用的目标必定已经在内存中存在。
初始化:
这是类加载阶段的最后一个阶段,前面的类加载阶段之后,除了加载阶段可以自定义类加载器外,其他操作都是由JVM主导,到了初始阶段,才真正执行Java代码。
初始化阶段是类构造器方法的过程,是由编译器自动收集类变量的复制操作和静态语句块的语句合并形成,虚拟机会保证方法执行前,父类方法已经执行完毕,如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成方法。
不执行类初始化的情况:
通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
定义对象数组,不会触发该类的初始化。
常量在编译期间会存入调用类的常量池中,本质上没有直接引用定义常量的类,不会触发定义常量所在的类。
通过类名获取Class对象,不会触发类的初始化。
通过Class.forName加载指定类时,如果指定参数initialize为flase时,也不会触发类初始化,这个参数是告诉虚拟机要对类进行初始化。
通过ClassLoader默认的loadClass方法,也不会触发初始化动作。
类加载器
虚拟机设计团队把加载动作放在JVM外部实现,以便让应用程序决定如何获取所需的类,JVM提供了三种类加载器。
- 启动类加载,负责加载Java HOME\lib目录中的,或者通过Xbootclasspath参数指定路径中的,且被虚拟机认可的类。
- 扩展类加载器,负责加载JAVA_HOME\lib\ext目录中的,或通过java.ext.dirs系统变量指定路径中的类库。
- 应用程序类加载器,负责加载用户路径上的类库。
JVM通过双亲委派模型进行类的加载,当然我们可以通过继承java.lang.ClassLoader实现自定义的类加载器。
双亲委派
一个类收到类加载请求,他首先不会尝试自己加载这个类,而是把这个类委派给父类去完成,每一个曾次类加载器都是如此,因此所有的加载请求都应该传送到启动类加载其中,只有当父类加载器反馈自己无法完成这个请求的情况下,子类加载器才会尝试自己完成。
采用双亲委派的好处是哪个加载器加载哪个类,最终都是委托给启动类加载器进行加载,保证了不同类使用加载时都是同一个Object对象。
OSGI
Open Service Gateway Initiative是面向Java的动态模型系统,是Java动态化模块化系统的一系列规范。
OSGI服务平台提供在多种网络设备上无需重启的动态改变构造的功能,为了最小化耦合度和促使这些耦合度可管理,OSGI技术提供了一种面向服务的架构,它能使这些组件动态地发现对方。
OSGI旨在实现Java程序的模块化编程提供基础条件,基于OSGI的程序很可能可以实现模块级的热插拔功能,当程序升级更新的时候,只停用,重新安装,然后启动程序中一部分,这对企业级程序开发是非常诱惑的。
OSGI描绘了一个很美好的模块化开发目标,定义实现了这个目标所需要的服务和框架,同时也由成熟的框架实现支持,但并非所有的应用都都适合采用OSGI作为基本架构,它在提供强大功能的时候也引入了额外的复杂度,因为它不遵守类加载的双亲委派模型。