类加载机制
整个生命周期包括加载(loading),验证(verification),准备(preparation),解析(resolution),初始化(initialization),使用(using),卸载(unloading)
加载
虚拟机的规范要求:
- 根据全限定名加载类的二进制字节流
- 将静态数据结构转换为方法区的运行时数据结构
- 生成代表这个类的java.lang.Class对象,作为该类在方法区的结构入口
其中,数组类本身是不通过类加载器加载的,但是数组类的元素类型是通过加载器来完成
验证
保证加载的字节流不会对虚拟机产生破坏,从下面四个方向验证:
- 文件格式验证:例如魔数,版本,常量池等
- 元数据验证:类的继承关系等
- 字节码验证:指令码等信息
- 符号引用验证:符号引用转直接引用的一些校验
准备
主角先明确一下:类变量(被static修饰的变量),针对主角有两个关键动作:
- 分配内存
- 初始值(各类型的0值)
解析
将常量池中的符号引用替换为直接引用的过程,那解析的时机是什么?在操作符号引用的字节码指令之前进行解析,所以就看什么时候会触发那16个指令
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
初始化
真正执行类中定义的Java代码,是执行类构造器<clinit>方法的过程,clinit是由类变量赋值和静态代码块合并产生:
- 编译器在收集是有顺序的,按照原文件的顺序决定,所以静态语句块只能访问它之前定义的静态变量
- 父类的clinit会在子类的clinit之前执行,所以父类的静态语句块先执行
类加载器
一个有意思的设计思路,刚才我们看过整个虚拟机加载机制,但对于如何根据全限定名去加载二进制流,jvm将这部分开放了出来,给我们提供了更灵活的设计。
类的唯一性
如果你要是使用instanceOf,Class对象的equals等判断实例是否相等,那需要考虑一下是不是一个类加载器加载的class文件(二进制字节流),因为jvm认为只有类加载器+类全限定名才能确认类的唯一性。
双亲委派
先看一下类加载器吧:
- 启动类加载器
- 扩展类加载器
- 应用程序(系统)类加载器
双亲委派:类加载器收到加载请求时,先把请求让给老爹干,所以所有的活都会最终交给祖宗来干,但是祖宗(老爹)也不傻,每一个加载器都给自己画个圈(比如rt.jar),我就能干这个范围的活,剩下的年轻人自己干吧,上面我们提到了类的唯一性,这样甩锅的方法也有助于确定类的唯一性,否则就乱了;另外,值得注意的是,父加载器和子加载器不是我们想象的继承而是使用的组合模式。