生命周期
什么是类加载机制?
虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
1.类加载时机
主动引用
1)遇到以下字节码指令时
字节码指令 | java代码场景 |
---|---|
new | 使用new关键字实例化对象 |
getstatic | 读取一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外) |
putstatic | 设置一个类的静态字段 |
invokestatic | 调用一个类的静态方法 |
2)使用java.lang.reflect包的方法对类进行反射调用的时候
3)当初始化一个类的时候,如果父类还没进行过初始化,则先初始化父类
4)当jvm启动时,用户需要制定一个要执行的主类,jvm会先初始化这个主类
5)当使用JDK 1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。
被动引用
除上面5类引用外,所有引用类的方式都不会触发初始化
2.类加载过程
1. 加载
- 通过一个类的全限定名来获取此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
1.1.加载数组类(下面简称为C)创建过程就遵循以下规则:
- 数组类本身不通过类加载器创建,它是由Java虚拟机直接创建的。
- 数组类的元素类型(Element Type,指的是数组去掉所有维度的类型)最终是要靠类加载器去创建。
- 如果数组的组件类型(Component Type,指的是数组去掉一个维度的类型)是引用类型,那就递归采用本节中定义的加载过程去加载这个组件类型,数组C将在加载该组件类型的类加载器的类名称空间上被标识(一个类必须与类加载器一起确定唯一性)。
- 如果数组的组件类型不是引用类型(例如int[]数组),Jvm将会把数组C标记为与BootstrapClassLoader(引导类加载器)关联。
- 数组类的可见性与它的组件类型的可见性一致,如果组件类型不是引用类型,那数组类的可见性将默认为public。
2. 验证
目的:为了确保class文件的字节码包含的信息符合jvm的要求,并且不会危害jvm安全。
4个阶段验证动作
文件格式验证
目的:保证输入的字节流能正确地解析并存储于方法区之内元数据验证
目的:对类的元数据信息进行语义校验,保证不存在不符合java语言规范的元数据信息字节码验证
目的:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的符号引用验证
发生在虚拟机将符号引用转化为直接引用的时候-这个转化工作在解析阶段中发生
目的:确保解析动作能正常执行。
3. 准备
正式为类变量分配内存并设置类变量初始值的阶段,这些变量所使用的内存都将在方法区中进行分配。
这里所说的初始值“通常情况”下是数据类型的0值
最后应该是0
如果类字段的字段属性表中存在ConstantValue属性,那在准备阶段变量value就会被初始化为ConstantValue属性所指定的值
|
|
final属性的初始化动作必须在构造方法return之前完成
对象的创建和赋值给一个引用是两个动作
4. 解析
Jvm将常量池内的符号引用替换为直接引用的过程。
符号引用:以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
直接引用:直接指向目标的指针、相对偏移量或是一个能简洁定位到目标的句柄。
解析动作主要针对以下
- 类或接口的解析
- 字段解析
- 类方法解析
- 接口方法解析
5.初始化
如果一个类中没有静态语句块,也没有对变量的赋值操作,那么编译器可以不为这个类生成
类会执行父类的
接口不会执行父接口的
构造方法会在多线程环境中被加锁,只有有一个线程去执行
读后感笔记来自周志明的《深入理解Java虚拟机(第2版)》