文章目录
初始jvm
1、什么是jvm
JVM是虚拟机的英文简称。它是java运行环境的一部分,是一个虚构出来的计算机,它是通过在实际的计算机上仿真模拟各种计算机功能来实现的。直观的理解自己window系统上面安装Linux虚拟机。
为什么Java代码能一次编译到处运行,因为我们在安装了jdk’环境,其实就是安装了一台虚拟机,而我们的Java程序就是在这个虚拟机上运行的。由Java虚拟机通过机器码运行操作系统
2、机器码与字节码
机器码是电脑CPU直接读取运行的机器指令,运行速度最快,但是非常晦涩难懂,也比较难编写,一般从业人员接触不到。
字节码是一种中间状态(中间码)的二进制代码(文件)。需要直译器转译后才能成为机器码。
3、常说的jdk、jre与jvm之间的关系
大多数JDK都是在OpenJDK的基础上编写实现的, 比如IBM J9, Azul Zulu, Azul Zing和Oracle
JDK. 几乎现有的所有JDK都派生自OpenJDK,而我们常用的虚拟机通常是HotSpot虚拟机。本文也是依据hotspot虚拟机。
通常我们安装的jdk就是也是openjdk,而我们运行一个程序就会有开启一个jvm。
jvm的架构
自己画了一个Java的架构图,实在是看不下去,然后再网上找了一下,这是我觉得画得挺详细的一张。
想想我们开发代码的时候,结合我们中这张图,整理了一下开发到运行的整个流程。
JIT 编译器
程序的执行方式主要有三种:静态编译执行、动态编译执行和动态解释执行,此处所说的编译指的是编译成可让操作系统直接执行的机器码。
JIT编译器
在部分商用虚拟机中(如HotSpot),Java程序最初是通过解释器(Interpreter)进行解释执行的,
当虚拟机发现某个方法或代码块的运行特别频繁时,就会把这些代码认定为“热点代码”。
为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器(Just In Time Compiler,下文统称JIT编译器)
JIT是属于动态编译方式的,动态编译(dynamic compilation)指的是“在运行时进行编
译”;
与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static
compilation)
热点代码
程序中的代码只有是热点代码时,才会编译为本地代码,那么什么是热点代码呢?
运行过程中会被即时编译器编译的“热点代码”有两类:
- 被多次调用的方法。
- 被多次执行的循环体。
两种情况,编译器都是以整个方法作为编译对象。 这种编译方法因为编译发生在方法执行过程之中,
因此形象的称之为栈上替换(On Stack Replacement,OSR),即方法栈帧还在栈上,方法就被替换
了。
要知道方法或一段代码是不是热点代码,是不是需要触发即时编译,需要进行Hot Spot Detection(热点探测)
目前常用的热点探测方式有两种:
1)采样–周期性采样,经常出现再栈帧中的方法(缺点:热度不确定,容易线程阻塞)
2)计数器—为每个方法建立计数器,记录方法的调用次数,达到阈值(通常是1500)的方法,当计数器超过阈值溢出了,就会触发JIT编译hotspot采用就是此种方式。
解释器
但其实在我们hotspot虚拟机中不仅只有jit,还有一个解释器。基本上主流的Java虚拟机都采用解释器与编译器并存的架构。
无论是JIT编译器还是解释器,作用都是讲语言翻译为机器码,供操作系统执行。
解释器的执行,抽象的看是这样的:
输入的代码 -> [ 解释器 解释执行 ] -> 执行结果
而要JIT编译然后再执行的话,抽象的看则是:
输入的代码 -> [ 编译器 编译 ] -> 编译后的代码 -> [ 执行 ] -> 执行结果
JIT编译器的出现自身会耗费性能,所以只有对频繁执行的代码,JIT编译才能保证有正面的收益
解释器与编译器特点:
1)当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在
程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可
以获取更高的执行效率。
2)当程序运行环境中内存资源限制较大(如部分嵌入式系统中),可以使用解释器执行节约内存,
反之可以使用编译执行来提升效率。
JIT优化
由于即时编译,耗费性能,有时候得不偿失,hotspot虚拟机使用了很多的优化手段,建单介绍几种。
方法内联
在使用JIT进行即时编译时,将方法调用直接使用方法体中的代码进行替换,这就是方法内联,减少了
方法调用过程中压栈与入栈的开销。同时为之后的一些优化手段提供条件。如果JVM监测到一些小方法被频繁的执行,它会把方法的调用替换成方法体本身
例如:
private int add4(int x1, int x2, int x3, int x4) {
return add2(x1, x2) + add2(x3, x4);
}
private int add2(int x1, int x2) {
return x1 + x2;
}
# 替换为
private int add4(int x1, int x2, int x3, int x4) {
return x1 + x2 + x3 + x4;
}
逃逸分析
逃逸分析(Escape Analysis)是目前Java虚拟机中比较前沿的优化技术。这是一种可以有效减少Java
程序中同步负载和内存堆分配压力的跨函数全局数据流分析算法。通过逃逸分析,Java Hotspot编译
器能够分析出一个新的对象的引用的使用范围从而决定是否要将这个对象分配到堆上。
逃逸分析的基本行为就是分析对象动态作用域:当一个对象在方法中被定义后,它可能被外部方法所
引用,例如作为调用参数传递到其他地方中,称为方法逃逸
逃逸分析包括:
全局变量赋值逃逸
方法返回值逃逸
实例引用发生逃逸
线程逃逸:赋值给类变量或可以在其他线程中访问的实例变量
public class EscapeAnalysis {
//全局变量
public static Object object;
public void globalVariableEscape(){//全局变量赋值逃逸
object = new Object();
}
public Object methodEscape(){ //方法返回值逃逸
return new Object();
}
public void instancePassEscape(){ //实例引用发生逃逸
this.speak(this);
}
public void speak(EscapeAnalysis escapeAnalysis){ //无逃逸
Object obj= new Object();
}
}
同步锁消除
同样基于逃逸分析,当加锁的变量不会发生逃逸,是线程私有的完全没有必要加锁。 在JIT编译时期就
可以将同步锁去掉,以减少加锁与解锁造成的资源开销。
Java在后后面对synchornized锁的优化的时候时候就很好的用到了这一特性。