一、加载阶段
这里的加载是全体类加载过程中的一个阶段,不等同于类加载,在加载阶段,会做以下三件事:
1、 通过类的全限定名读取类的二进制流。
2、 将字节流所代表的的静态存储构造转化为方法区的运行时数据构造。
3、 在虚拟机内存的堆区中天生一个代表这个类的java.lang.Class工具,用于方法区这个类的各种数据的访问入口(如下图所示)。
由于Java虚拟机对加载class文件的来源并未做限定,以是涌现了以下的class文件加载办法:
1、 从本地系统中直接获取2、 从网络中获取,如:Web Applet3、 从zip压缩包中获取,将zip压缩后缀改为.jar,也可以直策应用4、 动态代理天生5、 由其他文件天生,如JSP6、 从数据库中获取7、 加密文件中获取,如Class文件加密防反编译
二、链接阶段在加载阶段完成之后,class文件的类信息数据就会存储在方法区,同时在Java虚拟机堆区天生一个对应类的Class工具,这个Class工具会在之后变成程序访问方法区中的类数据的外部接口。链接阶段并不是一定等到加载阶段完成后才开始,链接的部分动作会跟随加载阶段进行(如部分字节码文件格式的验证动作)。
1、验证验证是链接的第一个阶段,这个过程中,JVM会去校验class文件格式及class文件二进制流中所包含的信息是不是符合虚拟机规范的约束。包含四部分内容的验证:
文件格式验证:验证class文件魔数值是否为0xCAFEBABE、主次版本号、常量类型等。
元数据验证:对类的元数据信息进行语义校验。
字节码验证:通过数据流剖析和掌握流剖析、确定程序语义是合法的、符合逻辑的。
符号引用验证:验证发生在解析阶段,紧张对常量池中的各种符号引用进行匹配性校验。
2、准备在准备阶段,会给类变量(被static润色的静态变量)分配内存并且初始化类变量初值(零值),如上表便是各种类型对应的零值,从观点上讲,这些变量所利用的内存都应该在方法区中进行分配,但是方法区本身是一个逻辑上的区域,在JDK7及之前,HotSpot利用永久代来实现方法区时,实现是完备符合这种逻辑观点的;而在JDK8及之后,类变量则会随着Class工具一起存放在Java堆中,这时候"类变量在方法区" 就完备是一种对逻辑观点的表述;
把稳:
1、final润色的类变量(常量)并不会进行准备阶段进行赋初值的操作,在编译的时候会给属性添加ConstantValue属性,准备阶段直接完成赋值。
2、正由于类变量拥有赋初值这一操作,以是只声明类变量,不进行赋值动作,程序也能正常实行,如下代码可以验证各种类型的初值。
public class ClassLoaderPrepare { public static int i; public static void main(String[] args) { System.out.println(i); }}
3、解析
解析阶段的浸染是将符号引用转为直接引用。每个class文件都对应一个常量池,常量池中存储了类、接口、字段、方法等各种信息,符号引用是一组符号指向常量池中被引用的目标,要在虚拟机中定位到目标,就须要指向对应目标的内存地址,这种引用便是直接引用。
三、初始化阶段在链接阶段的准备阶段中,已经为类变量分配了内存地址和初值,在初始化阶段就会对这些类变量进行赋值操作。如果一个类含有静态变量或者静态代码块,java虚拟机就会在编译为其天生一个方法(类初始化方法),其内容由编译期间虚拟机网络到的类变量的赋值动作和静态代码块合并而来。
把稳:
1、<clinit>方法中,指令的顺序是依据指令对应的语句在源文件中涌现的顺序,静态代码块中只能访问定义在它之前的变量,如下代码就会提示造孽的前向引用。
2、在继续关系中,父类的<clinit>方法先于子类实行。
3、在多线程同时初始化一个类时,只有个中一个线程能够实行<clinit>
public class ClassLoaderCLInit { static { i = 10; System.out.println(i); } public static int i;}