初始化的五种情况加载阶段验证阶段准备阶段解析阶段符号引用和直接引用类或接口的解析字段解析类方法解析接口方法解析
类加载机制
类的生命周期
- 加载 2. 验证 3. 准备 4. 解析 5. 初始化 6. 使用 7. 卸载
初始化的五种情况
有且仅有五种情况会立即对类进行初始化,此五种情况称为对一个类的主动引用,其余均为被动引用
-
遇到new、getstatic、putstatic、invokestatic这四条字节码指令时
-
使用java.lang.reflect包方法对类进行反射调用的时候,如果类没有进行过初始化,则需要将其初始化
-
当初始化一个类的时候,如果父类没有初始化,则需要先触发父类的初始化(接口在初始化时并不会要求其父类全部已经完成初始化,只有真正使用到父接口的时候(如引用接口中定义的常量)才会初始化)
-
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的类),虚拟机会先初始化主类
-
当使用jdk1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄
加载阶段
”加载“是”类加载过程的一个阶段
-
加载阶段虚拟机需要完成以下三个步骤
-
通过一个类的全限定名来获取定义此类的二进制字节流(来源:zip包、网络、运行时计算生成、数据库读取等)
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
-
数组类本身不通过加载器加载,它是由java虚拟机直接创建的
1.如果数组类的组件类型是引用类,那就可以采用,那就会递归采用上述加载过程,去加载这个组件类型,数组类将在加载该组件类型的类加载器上被标识
2.如果数组类的组件类型为非引用类型(如int[],byte[]等),Java虚拟机会把数组类标记为与引导类加载器关联
3.数组类的可见性与它组件类型的可见性一致,如果组件类型不是引用类型,那其可见性就将默认为public</pre>
验证阶段
虽然Java语言是相对比较安全的语言,但是由于加载阶段的字节流来源多样,在字节码语言层面上,会有很多Java禁止的不安全操作(如访问数组边界外的数据,将一个对象转型为它从未实现过的类等),因此验证字节流是虚拟机对自身的一个保护机制,免受恶意代码攻击。
-
验证的四个阶段
-
文件格式验证
-
元数据验证
-
字节码验证
-
符号引用验证
-
文件格式验证阶段
主要验证字节流是否符合Class文件格式的规范和是否能被当前虚拟机处理
元数据验证阶段
对字节码描述的信息进行语义分析,主要是对数据类型做校验,以保证其描述的信息符合Java语言规范的要求。
如:
- 这个类是否有父类(除了java.lang.Object)都应该有父类 2. 非抽象类是否已全部实现父类中要求实现的方法 3. 是否继承了不被允许继承的父类(被final修饰的类)
...
字节码验证
第三阶段的校验最为复杂,主要目的是通过数据流和控制流的分析对类的方法体进行校验分析。
-
保证不会出现跳转指令跳转到方法体以外的字节码指令上
-
保证方法体中的类型转换有效
-
保证不会出现类似于一long类型来载入int类型数据
...
符号引用验证
发生在虚拟机将符号引用转化为直接引用的时候,在解析阶段中发生。符号引用可以理解为对类自身以外的信息进行匹配性校验,目的是确保解析动作能够正常执行
-
校验内容
-
符号引用通过字符串描述的全限定名能否找到对应的类
-
符号引用中的类、字段、方法的访问性是否可被当前类访问
-
准备阶段
准备阶段是正式为类变量(被static修饰,不包含实例变量)分配内存和设置类变量初始值的阶段,这些变量所使用的内存都将在方法区分配。
初始值”通常情况“下是数据类型的零值,”特殊情况“是当变量被定义为ConstantValue(被final修饰),则会根据其设置将value赋值
例:public static final int value = 123; 准备阶段value被赋值为123
解析阶段
解析阶段将常量池内的符号引用替换为直接引用的过程。
解析阶段发生的具体时间并未明确规定,只要求了在执行anewarray、checkcast、getfield、getstatic、instanceof、invokedynamic、invokeinterface、invokespecial、invokestatic、invokevirtual、ldc、ldc_w、multianewarray、newputfield和putstatic这16个用于操作符号引用的字节码指令之前,先对他们所使用的符号引用进行解析。
符号引用和直接引用
-
符号引用:
以一组符号来描述所引用的目标,可以是任何形式的字面量(字面量形式已在Class格式规范中定义,因此具有一致性,可被各种虚拟机接受),只要能无歧义地定位到目标即可。
-
直接引用:
是可以直接指向目标的指针、相对偏移量、或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。
符号引用的解析指令
invokedynamic指令 其它指令 动态(必须等到程序运行到这条指令的时候才执行) 静态(可以在刚完成加载阶段,还没开始执行代码阶段解析) 该指令的目的用于动态语言支持,遇到已被invokedynamic指令解析过得符号引用(“动态调用点限定符”),不意味着解析结果能对其它invokedynamic指令生效。 可以对第一次解析结果进行缓存(在常量池记录直接引用,并把敞亮表位已解析状态),从而避免重复解析。需要保证同一实体内,对同一符号引用,解析成功/异常是一致的。
类或接口的解析
image-20180813232413573.png字段解析
字段解析会首先对字段表内的class_index项中索引的CONSTANT_Class_info符号引用进行解析(即对字段所属的类),对所属类做以下后续字段的搜索
image-20180813231754630.png
如果查找过程成功返回了引用,则对这个字段进行权限验证。如果发现对这个字段不具有访问权限,则抛出java.lang.IlleagalAccessError。
类方法解析
类的解析的第一阶段与字段解析一样。
image-20180814000700759.png
接口方法解析
大致与类方法相似,由于借口不存在父类只存在父接口,只缺少对父类递归搜索处理。
网友评论