JVM类加载过程
2023-12-26 19:00:42 29 举报
JVM类加载过程
作者其他创作
大纲/内容
java/lang/Object
#29 // java/io/PrintStream
文件格式验证
解析失败
当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化
验证
类C本身是否包含简单名称和描述符都与目标相匹配的方法?
C是接口?
Class
是
在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
类加载子系统
......
常量池的常量中是否有不被支持的常量类型(检查常量tag标志)
符号引用验证,D是否具备对C的访问权限?
抛出java.lang.IllegalAccessError异常
Ljava/io/PrintStream;
类A的静态变量1
解析过程是否出现异常?
否
#24.#25 // java/io/PrintStream.println:(I)V
<clinit>()方法对于类或接口来说并不是必需的,如果一个类中没有静态初始化块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成<clinit>()方法。
putstatic:设置一个类的静态字段
运行时常量池
(BSCIJFDLjava/lang/Object;)I
什么时候进行加载阶段虚拟机规范并没有强制约束
Math.java
java/lang/System
执行<clinit>()方法
解析字段所属的类或接口C的符号引用N
接口的<clinit>()方法执行之前,不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量使用时,父接口才会初始化。
Utf8
Fieldref
加载过程是否出现异常?
访问类C的字段A,如果类C中没有该字段,而类C的父接口和父类中同时有字段A,就会报编译错误:The field A is ambiguous
父类中包含了简单名称和描述符都与目标相匹配的方法?
通过一个类的全限定名来获取定义此类的二进制字节流
(I)V
#27:#28 // out:Ljava/io/PrintStream;
C是java.lang.Object?
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有进行过初始化,则需要先触发其初始化
()V
解析成功
<init>
返回这个字段的直接引用
生成<clinit>()方法
test/Math
类A的类构造器方法<clinit>
为静态变量分配内存并设置初始值
类C是否实现了接口?
结束
按照继承关系从下往上递归搜索各个接口和它的父接口
SourceFile
#30:#31 // println:(I)V
加载
抛出java.lang.IncompatibleClassChangeError异常
类的<clinit>()方法执行之前,虚拟机会保证它的父类的<clinit>()方法已经执行完毕。
#8:#9 // \"<init>\":()V
这个类的父类是否继承了不被允许继承的类(被final修饰的类)
虚拟机规范严格规定了“有且只有”5种情况必须立即对类进行初始化,而加载、验证、准备阶段需要在此之前开始
主次版本号是否在当前虚拟机处理范围之内
保证跳转指令不会跳转到方法体以外的字节码指令上
当虚拟机启动时,用户需要指定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个主类
字节码验证
实例数据
Methodref
getstatic:读取一个类的静态字段(被final修饰、已在编译期把结果放入常量池的静态字段除外)
#2.#21 // test/Math.math:(BSCIJFDLjava/lang/Object;)I
将虚拟机外部的二进制字节流按照虚拟机所需的格式存储在方法区之中
类A的静态字段
抛出java.lang.NoSuchMethodError异常
直接和间接父类的所有实例成员变量
符号引用验证
java/io/PrintStream
接口中包含简单名称和字段描述符都与目标相匹配的字段?
当前代码所处的类为D,要把一个从未解析过的符号引用N解析为一个类或接口C的直接引用
println
符号引用中的类、字段、方法的访问性(private、default、protected、public)是否可被当前类访问
([Ljava/lang/String;)V
类A的静态方法
#20 // java/lang/Object
类或接口的解析
#12:#13 // math:(BSCIJFDLjava/lang/Object;)I
验证字节流是否符合Class文件格式的规范,并且能被当前版本的虚拟机处理
卸载
Class对象指针
#19 // test/Math
value2设置为123
返回这个方法的直接引用
public static int value1 = 123;
一个类(下面简称A)的字节码
准备
类加载过程
类A的字段信息
按照继承关系从下往上递归搜索其父类
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
类A的静态变量3
接口方法解析
C是类?
Mark Word
字段表
类方法解析
解析接口方法所属的类或接口C的符号引用N
类A的静态变量2
类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一样,但返回值类型却不同等)
#2.#18 // test/Math.\"<init>\":()V
对字节码描述的信息进行语义分析,以保证其描述的信息符合Java语言规范的要求
如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法
父接口中包含了简单名称和描述符都与目标相匹配的方法?
main
解析类方法所属的类或接口C的符号引用N
通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
LineNumberTable
类A的实例字段
C本身是否包含简单名称和字段描述符都与目标相匹配的字段?
抛出java.lang.AbstractMethodError异常
字段解析
Code
类A的实例构造器方法<init>
解析
math
虚拟机生成一个代表此数组维度和元素的数组对象
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
out
C中是否实现了接口?
接口的实现类的<clinit>()方法执行之前,不需要先执行接口的<clinit>()方法,只有当接口中定义的变量使用时,父接口才会初始化。
是否以魔数0xCAFEBABE开头
接口C是否有父接口?
正式为类变量分配内存并设置类变量初始值
NameAndType
接口C本身是否包含简单名称和描述符都与目标相匹配的方法?
new:实例化一个对象
<clinit>()方法与实例构造器<init>()方法不同,它不需要显式地调用父类的<clinit>()方法,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。因此在虚拟机中第一个被执行的<clinit>()方法的类肯定是java.lang.Object
将N传递给D的类加载器去加载类C
指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量
保证方法体中的类型转换是有效的,例如可以把一个子类对象赋值给父类数据类型,这是安全的,但是把父类对象赋值给子类数据类型,甚至把对象赋值给与它毫无继承关系、完全不相关的一个数据类型,则是危险的和不合法的
加载数组元素类型(递归解析)
接口中不能使用静态初始化块,但仍然有变量初始化的赋值操作,因此接口与类一样都会生成<clinit>()方法。
父类中包含了简单名称和字段描述符都与目标相匹配的字段?
接口中包含简单名称和描述符都与目标相匹配的方法?
类A的方法信息
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕。当执行<clinit>()方法的那条线程退出<clinit>()方法后,其他线程唤醒之后不会再次进入<clinit>()方法。同一个类加载器下,一个类型只会初始化一次。
使用
编译器按照语句在源文件中出现的顺序收集类中所有类变量的赋值语句和静态初始化块中的语句,合并产生<clinit>()方法。
invokestatic:调用一个类的静态方法
当前类是否具备对该方法的访问权限?
CONSTANT_Utf8_info型的常量中是否有不符合UTF8编码的数据
虚拟机将常量池中的符号引用替换为直接引用
访问类C的字段A,如果类C中没有该字段,对类C的各个父接口进行递归搜索,如果搜索结果指向不同的接口,就会报编译错误:The field A is ambiguous
类A的实例方法
初始化
#26 // java/lang/System
对齐填充
C是一个数组类型吗?
符号引用验证可以看作是对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
类A的类型信息
类A的二进制字节流
当前类的所有实例成员变量
保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作,例如不会出现类似这样的情况:在操作数栈放置了一个int类型的数据,使用时却按long类型来加载入本地变量表中
方法区
确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机的安全
符号引用中通过字符串描述的全限定名是否能找到对应的类
元数据验证
public static final int value2 = 123;
静态初始化块中只能访问到定义在静态初始化块之前的类变量,定义在它之后的类变量,在前面的静态初始化块可以赋值,但是不能访问
#4.#18 // java/lang/Object.\"<init>\":()V
这个类是否有父类(除了java.lang.Object之外,所有的类都应当有父类)
Class文件中各个部分及文件本身是否有被删除或附加的其他信息
使用JDK1.7的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而这个方法句柄所对应的类还没有进行过初始化,则需要先触发其初始化
value1设置为0
遇到new、getstatic、putstatic、invokestatic这四条字节码指令时,如果类没有进行过初始化,则需要先触发其初始化
按照继承关系从下往上递归搜索其父接口,直到java.lang.Object类(查找范围会包括Object类)
#22.#23 // java/lang/System.out:Ljava/io/PrintStream;
连接
当前类是否具备对该字段的访问权限?
初始化阶段是执行类构造器<clinit>()方法的过程
0 条评论
下一页