类加载过程
一、类加载的时机
- 场景遇到 new、getstatic、putstatic、invokestatic 四条字节码指令
- 使用new关键字实例化对象
- 读取/设置静态字段(除常量池内的静态字段)
- 调用静态方法
- 使用java.lang.reflect包对类型进行反射调用
- 初始化子类的时,发现未初始化父类
- 虚拟机启动时,初始化主类(main方法所在类)
- java.lang.invoke.MethodHandle最后解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic、REF_newInvokeSpecial的方法句柄对应的类未初始化(JDK 7 动态语言)
- 使用default关键字的接口,实现类初始化时该接口未初始化(JDK 8)
二、类加载的过程
类的生命周期
加载
- 通过一个类的全限定名来获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在方法区中生成一个代表这个类的 java.lang.Class 对象, 作为这个类的各种数据的访问入口
验证
确保 class 文件的字节流包含的信息符合《Java虚拟机规范》的全部约束要求,保证这些信息被当成代码运行后不会危害虚拟机自身的安全;
- 文件格式验证
验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理 - 是否以魔数0xCAFEBABY开头
- 主次版本号是否在当前Java虚拟机接受范围之内
- 常量池的常量中是否有不被支持的常量类型
- 指向常量的各种索引值中是否有指向不存在的常量
- CONSTANT_Utf8_info型的常量中是否有不符合UTF-8编码的数据
- Class文件中各个部分本身是否有被删除的或附加的其他信息
- ...
- 元数据验证
对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java语言规范》的要求 - 这个类是否有父类(除Object之外,都应该有父类)
- 这个类的父类是否继承了不允许被继承的类
- 如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
- 类中的字段、方法是否与父类生产矛盾
- ...
- 字节码验证
整个验证过程中最复杂的一个阶段,主要目的是通过数据流分析和控制流分析,确定语义是合法的、符合逻辑的
注:JDK6以后绝大多数字节码校验在javac编译器中处理,方法体Code属性表中新增了一项名为StackMapTable的属性,用于描述方法体所有的基本块开始时本地变量和操作栈应有的状态 - 符号引用验证
验证该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源 - 符号引用中通过字符串描述的全限定名是否能找到对应的类
- 在指定类中是否存在符合方法的字段描述符及简单名称所描述的方法和字段
- 符号引用中的类、字段、方法的可访问性是否可被当前类访问
- ...
准备
正式为类中定义的静态变量分配内存并设置零值;
注:JDK8 以前在永久代中分配内存,JDK8 以后在堆中分配内存
解析
Java虚拟机将常量池内的符号引用替换为直接引用
- 类 || 接口解析
- 字段解析
- 方法解析
- 接口方法解析
初始化
执行类构造器<clinit>()
注:1、<clinit>() 由编译器自动收集类中所有类变量的赋值动作和 static{} 块合并产生;
2、子类的<clinit>()方法执行前会先去执行父类的<clinit>()方法;
3、只有但接口中定义的变量被使用时,接口才会被初始化
4、<clinit>()方法线程安全
参考资料:深入理解Java虚拟机第3板


浙公网安备 33010602011771号