前言
上文介绍了class的结构和字节码指令集,本文将介绍jvm的类加载过程。
类的生命周期
一个类从被加载到jvm的内存知道被jvm卸载出内存为止,一共要经历加载,验证,准备,解析,初始化,使用,卸载七个阶段,这七个阶段完整的构成了一个类的生命周期,其中验证,准备,解析这三个阶段统称连接。
其中,加载,验证,准备,初始化,卸载的五个阶段顺序是确定的,解析有些场景可以放到初始化之后进行操作。
类加载到jvm的主要阶段
具体介绍类加载过程中每个重要阶段
加载类的时机
对于第一个加载阶段,java虚拟机规范没有强制规定何时开始,可以由各大虚拟机自己决定,但是规定了有六种情况必须对类进行初始化,而完成初始化之前,就必须要走完加载,验证,准备这些阶段。
-
遇到四种字节码指令
- new指令即new 关键字实例化对象的时候
- getstatic读取或设置一个类型的静态字段 (被final修饰,或者编译期结果加入静态常量池的除外)的时候
- putstatic or invokestatic 调用一个类型的静态方法时候
-
使用java 的反射技术调用类的时候,如果类没有初始化,即初始化类
-
初始化类时,发现其父类没有初始化,初始化父类
-
jvm启动时初始化包含main方法的主类
-
jdk7以上,对个java.lang.invoke.MethodHandle实例最后的解析结果为REF_getStatic、REF_putStatic、REF_invokeStatic,REF_newInvokeSpecial四种类型的方法句柄,并且这个方法句柄对应的类没有进行过初始化,则需要先触发其初始化。
-
jdk8以上,接口加入了default的方法,有其实现类初始化,在其初始化之前,初始化该接口。
加载
类进入jvm中的第一个阶段,这个阶段jvm主要完成三件事情
- 通过一个类的全限定名来获取该类的二进制字节流
- 将这字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 内存中生成的一个代表该类的java.lang.class对象,作为方法区各个类的各种数据的访问入口。
验证
验证阶段主要是验证class文件的字节流包含的信息全部符合jvm的规定,确保运行这些信息不会危害到jvm的安全。验证阶段大概有四个验证流程
- 文件格式验证, 验证字节流是否符合class文件格式的规范,包括但不限于以下的验证点,魔数验证,版本号验证,常量池的是否有不支持的常量等。
- 元数据验证,对字节码描述的进行进行语义验证,确保其描述的信息符合java虚拟机规范,包括但不限于以下的验证点,类是否有父类,类是否抽象,这个类的父类是否继承了不许被继承的类,非抽象的类是否实现父类或者实现街路口的方法等。
- 字节码验证,通过数据流分析和控制流分析,确保程序的语义是合法的,符合逻辑的,元数据验证对数据类型进行了校验,字节码验证就是对方法体进行校验。
- 符号引用验证,对类自身以外的(类中常量池引用的)各种类型进行校验,简单讲就是验证该类是否访问或者引用了一些禁止访问的类,字段,接口等。
准备
准备阶段即正式为类中的静态变量进行初始化赋值操作,赋的值只是初始值,并不是开发者定义的值。如开发者定一个 static int c = 10
;
这个阶段会对c进行初始化赋值为0,int类型的初始值就是0.
解析
解析阶段做的是将常量池的符号引用替换位直接引用的过程。简单介绍一下符号引用就是用一组符号来描述所引用的目标,而直接引用则是指向目标的指针,相对偏移量或者能定位到目标的一个句柄,如果存在符号引用,不一定说明虚拟机内存在引用的目标,但存在直接引用,该虚拟机内一定存在引用的对象。
而将符号引用转化为直接引用,就需要去解析符号引用的字面量,解析的过程主要为类或接口解析,字段解析,方法解析等,其实现原理也是根据表信息进行查询然后验证。
初始化
初始化作为整个类加载过程中的最后一个阶段,具体就是执行java中的代码,比如之前提到的给 static int c =3; 在准备阶段,jvm给它赋了int的初始值0,在初始化阶段给它赋值代码中定义的值3.
总结
本文详细的介绍了一个类被jvm加载的全部过程。