目录:
SPI (service provider interface)
申明
本文很多的内容都是作者通过阅读《深入理解Java虚拟机[JVM高级特性与最佳实践](周志明)》这本书学习到的。如果需要关于类加载更加详细的细节信息,请自行去阅读JVM 相关书籍
JVM类加载以及SPI
类的生命周期:
说明:只有加载阶段用户可以通过自定义类加载器参与,别的阶段都是虚拟机自己主导的
1.加载
- 通过类的全限定名获取描述此类的二进制字节流(获取方式任意—这是一个开放式规范 比如:动态代理,网络传输,zip包中读取比如jar,war。从数据库中读取……)
- 将字节流所代表的静态存储结构保存为方法区的运行时数据结构(1.8以后存在meta space,1.7 开始 类的静态量以及 常量 都存在 head 区)
- 在java head 创建一个这个类的java.lang.Class 对象,作为访问 方法区(1.8 meta space )的入口
说明:后面的所有加载操作我都统称loading
2.验证
- 格式验证:验证字节流是否是符合class文件格式规范,是否能被当前虚拟机处理
- 是否以魔数0xCAFEBABE开头(这个是jvm 验证的一个标准 )
- 主次版本号是否在当前虚拟机的处理范围(1.7的代码,你用1.6 jvm 编译肯定编译不了,这个就是验证这类版本号的)
- 常量池的常量中是否有不被支持的常量类型(检查常量的tag标志)
- Class文件中各部分及文件本身是否有被删除或附加其他信息
- ……….
- 元数据验证:对字节码描述信息进行语意解析,以保证其描述的信息符合java语言规范要求
- 这个类是否有父类(除了Object所有类又应该有父类)
- 这个类是否继承了不允许被继承的类(比如final 修饰的类就不能被继承)
- 如果这个类不是abstract 类,是否实现了父类或接口中要求实现的方法
- ……..
提示:所有类最终父类都是Object
- 字节码验证:这是最复杂的一个阶段,主要进行数据流和控制流分析。该阶段将会对类的方法体进行分析,以保证被校验类的方法在运行时不会做出危害虚拟机安全的行为
- 保证方法体中的类型转换是有效的,比如把一个父类赋值给子类,或则把一个类赋值给一个毫无关系的类,这样的操作就是不合法的
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,而不会出现像:在操作数栈放一个int数据,使用时却按照long来加载进入局部变量表中
- ……
- 符号引用验证:这个阶段的校验发生在虚拟机将符号引用转换为直接引用的时候(也就是在解析阶段的时候)
- 符号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定类中是否存在符合方法的符号的字段描述符以及简单的名称描述的方法和字段
- 引用中的类、字段、方法的访问性(public,private…)是否能被当前类访问
类加载器以及双亲委派模型
上面的加载阶段的第一步,通过类的全限定名获取描述此类的二进制字节流。这个动作,就是对用户开放的一个动作,在这个阶段java 平台开发者 可以自定义自己的类加载器,方法是有很多种。
这里说一下 双亲委派模型 以及下面的SPI 规范的一种方式
双亲委派模型
- 首先 从java 虚拟机角度来说分为两种类加载器:
- 启动类加载器(Bootstrap ClassPoader)这个类加载器由C++实现,虚拟机的一部分
- 其他所有的由java实现的类加载器
- 从java 开发人员的角度来看 类加载器分得更细一些,大多数时候我们会使用到系统提供的三种类加载器:
- 启动类加载器(Bootstrap ClassLoader)他负责加载<JAVA_HOME>/lib 目录中的,或则被-Xbootclasspath参数所指定的路径中被虚拟机识别的的jar(比如rt.jar,名字不符合的及时放在lib 也不会被加载的) 到虚拟机的内存中。
说明:启动类加载器无法被java程序直接引用
- 扩展类加载器(Extension ClassLoader)这个由sun.misc.Launcher$ExtClassLoader 实现,它负责加载<JAVA_HOME>/lib/ext 下面的jar 。或则被java.ext.dirs 系统变量所指定的路径中的所有类库
- 应用类加载器(Application CLassLoader)这个sun.misc.Launcher$AppssLoader
实现,这个类的实例是ClassLoader.getSystemClassLoader()的返回值,所以也叫它系统类加载器,他负责加载classpath 下面的所有类,如果程序中没有自定义的类加载器,一般就是使用的这个类加载器了
- 模型图:
<