文章目录
- java解释执行
- 热点代码编译
- 类生命周期
- 加载(Loading)
- 验证(Verification)
- 准备(Preparation)
- 解析(Resolution)
- 初始化(Initialization)
- 使用(Using)
- 卸载(Unloading)
- lazyloading 懒加载
java解释执行
一个class文件load到内存之通过java的解释器bytecode intepreter解析,JIT(just in-time compiler)编译器值得是有些代码需要把它编译成为本地代码
java是编译语言还是解释语言?
默认情况是 混合模式=混合使用解释器+热点代码编译
热点代码编译
每个方法都有计数器,循环也有循环的计数器,在发现某个方法循环执行,会编译成本地代码,再次调用时直接用本地的,不用解释器执行
为什么不直接都编译成本地代码?执行效率更高
- java解释器现在效率也已经很高了,在简单的代码上不亚于编译器
- 如果有一段代码执行文件特别多,用了各种类库,编译过程会很长
类生命周期
一个类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期将会经历七个阶段
加载(Loading)
在加载阶段,Java 虚拟机需要完成以下三件事情:
-
通过这个类的全限定名来获取此类的二进制字节流。
-
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
-
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
对于数组类而言,情况有所不同,数组类本身不通过类加载器创建,它是由 Java 虚拟机直接在内存中动态构造出来的。
加载阶段结束后,Java 虚拟机外部的二进制字节流就按照虚拟机所设定的格式存储在方法区之中,在 Java 堆内存中实例化一个 java.lang.Class 类的对象,这个对象将作为程序访问方法区中的类型数据的外部接口。
方法区中的数据存储格式完全由虚拟机实现自行定义,《Java 虚拟机规范》未规定此区域的具体数据结构。
验证(Verification)
验证的目的是确保 Class 文件的字节流中包含的信息符合《Java 虚拟机规范》的全部约束要求,保证这些信息被当作代码运行后不会危害虚拟机自身的安全。
-
文件格式验证:
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。 -
元数据验证:
对字节码描述的信息进行语义分析,以保证其描述的信息符合《Java 语言规范》的要求。 -
字节码验证:
通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑的。 -
符号引用验证:
简单来说就是该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。此验证校验行为发生在虚拟机将符号引用转化为直接引用的时候,即「解析阶段」中进行。
准备(Preparation)
准备阶段是正式为类中定义的变量(静态变量)分配内存并设置类变量初始值的阶段,从概念上讲,这些变量所使用的内存都应当在方法区中进行分配。
解析(Resolution)
解析阶段是 Java 虚拟机将常量池内的符号引用替换为直接引用的过程。
解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符这 7 类符号引用进行
符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
符号引用与虚拟机实现的内存布局无关,引用的目标并不一定是已经加载到虚拟机内存当中的内容。
各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的,因为符号引用的字面量形式明确定义在《Java 虚拟机规范》的 Class 文件格式中。
直接引用:直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄。
直接引用是和虚拟机实现的内存布局直接相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那引用的目标必定已经在虚拟机的内存中存在。
初始化(Initialization)
初始化阶段就是执行类初始化代码,类中的静态代码块,构造代码块、构造方法都会执行、静态变量赋予初始值。
使用(Using)
卸载(Unloading)
lazyloading 懒加载
严格上讲应该叫lazyInitializing懒初始化。jvm虚拟机的实现都是用的懒加载,什么时候需要这个类才去加载,并不是说一个jar文件里面有1000个类但是只用到了其中一个类就要把1000个类都加载进来。
jvm规范并没有规定何时加载,但严格规定了什么时候初始化
- new getstatic putstatic invokestatic 指令(访问类或者接口的静态变量或者对该静态变量赋值,或者调用类的静态方法),访问final变量除外(比如:访问类的final静态常量)
- java.lang.reflect对类进行反射调用时,比如Class.forName(“xxx”)
- 初始化子类的时候,父类首先初始化
- 虚拟机启动时,被执行的主类必须初始化
- 动态语言支持java.lang.invoke.MethodHandle解析的结果为REF_getstatic REF_putstatic REF_invokestatic的方法句柄时,该类必须初始化
public static void main(String[] args) throws Exception{
// P p; // 无打印 没有new 没有访问不加载不初始化
// X x = new X(); // P X new 子类,先初始化父类
// System.out.println(P.i); // 8 final不需要加载类
// System.out.println(P.j); // P 9 非final得变量需要加载初始化
// Class.forName("com.jsonschema.Test$P"); //P 反射调用被加载初始化
// 无打印 动态加载只加载class,获得class对象,没有执行连接、初始化
ClassLoader.getSystemClassLoader().loadClass("com.jsonschema.Test$P");
}
public static class P{
final static int i = 8;
static int j = 9;
static {
//静态代码块在类加载时调用,且只调用一次
System.out.println("P");
}
}
public static class X extends P{
static {
System.out.println("X");
}
}