java代码首先要通过前端编译器编译成.class字节码文件,然后再按一定的规则加载到JVM(java 虚拟机)内运行,有三种运行方式,解释模式(javac)、编译模式(C1 JIT、C2 JIT)、混合模式(javac+(C1 OR C2))。解释模式下,一边执行字节码一边解释执行;编译模式下,字节码编译为机器码后执行;混合模式下,正常情况下使用解释执行,但是针对经常执行的代码,会采用JIT技术进行编译执行。无论是server运行模式下还是client运行模式下,都有可能采用解释+(C1 OR C2 )执行。但本文的重点不在执行,而是编译,包括前端编译器、C1 JIT、C2 JIT。
编译成功之后会通过JVM的验证对字节码(.class)文件进行验证
验证分为三部分
Linking1、验证(Verify):目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,保证被加载类的正确性,不会危害虚拟机自身安全。主要包含四种验证, 文件格式验证,元数据验证,字节码验证,符号引用验证。2、准备(Prepare) :为类变量分配内存并且设置该类变量的默认初始值,即零值。这里不包含用final修饰的static,因为final在编译的时候就会分配了,准备阶段会显示初始化;这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。3、解析(Resolve):将常量池内的符号引用转换为直接引用的过程。事实上,解析操作往往会随着JVM在执行完初始化之后再执行。符号引用就是一组符号来描述所引用的目标。符号引用的字面量形式明确定义再<<java虚拟机规范>> 的Class文件格式中。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型等、对应常量池中的CONSTANT_CLass_info、CONSTATNT_Fieldref_info、CONSTATNT_Methodref_info等。
Prepare 层 是指准备初始化: 变量默认设置初始值 String=null int =0 ......
在下一个阶段进行正真的赋值
可以通过反编译工具进行查看赋值的结果:
jclasslib : 反编译字节码文件的工具
打开工具把class文件拖拽进去可以看到
里面有<init> 则是你的构造方法的赋值
<clinit>: 则是你的公共变量的赋值 并且该变量的值记录也在其中
如:
public class ClinitTest{
private static int i=1;
static{
i= b; // 因为Prepare阶段会初始化的给一个值所以这里是可以赋值的,在Prepare下一个阶段会将下面定义的值替换
// System.out.println(b) // 这样做法是错误的可以替换值但不可以输出值因为犯了向前引用的错误
}
private static int b= 10;
}
则会将公共的变量打印出来
如果该变量被final修饰之后就算是常量所以只会在下一个阶段展示 Prepare只是预览