执行引擎是jvm执行java程序的一套子系统。
1 解释器
jvm中又两种解释器,字节码解释器、模板解释器,字节码解释器,解释执行,模板解释器执行编译后的硬编码(机器码)。
1.1 字节码解释器
java初期只有字节码解释器,字节码解释器是逐个将字节码指令翻译成硬编码然后执行,所以是
java字节码 -> c++ -> 硬编码过程;非常低效。
0 new #2 <java/lang/StringBuilder>
3 dup
4 invokespecial #3 <java/lang/StringBuilder.<init>>
7 ldc #4 <子牙>
9 invokevirtual #5 <java/lang/StringBuilder.append>
12 new #6 <java/lang/String>
15 dup
16 ldc #7 <真帅>
18 invokespecial #8 <java/lang/String.<init>>
21 invokevirtual #5 <java/lang/StringBuilder.append>
24 invokevirtual #9 <java/lang/StringBuilder.toString>
27 astore_1
28 goto 28 (0)
字节码解释器实现模板:
// c++ 代码
while(true) {
for() {
char code =
switch(code) {
case NEW:
// 解释执行
break;
case DUP:
break;
}
}
可以看出是字节码解释器是用c++代码逐个将字节码解释成硬编码后执行。
1.2 模板解释器
模板解释器直接执行编译过后的硬编码。
模板解释器底层执行流程:
1.申请一块内存,可读可写可执行
2.拿到代码的硬编码
3.将硬编码写入申请的内存
4.申请一个函数指针,指向这块内存
5.调用这个函数指针
1.2.1 即时编译
为了解决字节码解释器效率问题,jvm支持了即时编译技术,将整个函数体编译成机器码,模板解释器直接执行。目前有两种即时编译器,C1、C2。即时编译的最小单位不是一个函数,而是代码块(for、while)
1.2.1.1 jvm server模式和client模式的区别
Server模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升.原因是:当虚拟机运行在-client模式的时候,使用的是一个代号为C1的轻量级编译器, 而-server模式启动的虚拟机采用相对重量级,代号为C2的编译器. C2比C1编译器编译的相对彻底,服务起来之后,性能更高.
所以通常用于做服务器的时候我们用服务端模式,如果你的电脑只是运行一下java程序,就客户端模式就可以了。
1.2.1.2 C1编译器
C1编译器是client模式下的的即时编译器。
1、触发的条件相对C2比较宽松:需要收集的数据较少。
2、编译的优化比较浅:基本运算在编译的时候运算掉了。
3、C1编译器编译生成的代码执行效率较C2低。
1.2.1.2 C2编译器
C2编译器是server模式下的即时编译器
1、触发的条件比较严格,一般来说,程序运行了一段时间以后才会触发。
2、优化比较深。
3、编译生成的代码执行效率较C1更高。
1.2.1.3 混合编译
程序运行初期触发C1编译器。
程序运行一段时间后触发C2编译器。
1.2.1.4 编译优化
jvm在不同的编译器上还做了不同程度的优化,使效率更快
1.2.1.4.1 C1编译器优化
方法内联:将引用的函数代码编译到引用点处,这样可以减少栈帧的生成,减少参数传递以及跳转过程
去虚拟化:对唯一的实现类进行内联
冗余消除:在运行期间把一些不会执行的代码折叠掉
1.2.1.4.2 C2编译器优化
对于C2编译器的优化,是建立在逃逸分析的基础上的。
1.2.1.4.2.1 逃逸分析
逃逸分析是分析对象的作用域是否是局部的,会不会逃到方法外、线程外。
共享变量、返回值,参数就是逃逸的,对象的作用域是局部的就叫不逃逸
基于逃逸分析,C2编译器有3种优化技术,栈上分配、标量替换、锁消除。如果对象是逃逸的那么对象将变得比较复杂,优化无法实施。
逃逸分析默认是开启的,可通过下面命令开启和关闭:
开启:-XX:+DoEscapeAnalysis
关闭:-XX:-DoEscapeAnalysis
1.2.1.4.2.2 栈上分配
对象在虚拟机栈上分配。
证明栈上分配存在
public class AllocationOnStack {
public static AllocationOnStack allocation() {
return new AllocationOnStack();
}
//-Xmx100m -Xms100m -XX:-DoEscapeAnalysis -XX:+PrintGC 关闭逃逸分析
// 逃逸分析默认开启
public static void main(String[] args) {
long begin = System.currentTimeMillis();
for (int i = 0; i < 1000000; i++) {
allocation();
}
long end = System.currentTimeMillis();
System.out.println("耗时:"+(end-begin)+"ms");
while (true);
}
}
以上代码如果开启了逃逸分析,在没有进行垃圾回收的情况下那么在堆中就没有100w个AllocationOnStack对象。
执行后通过jstat查看未促发GC,通过HSDB查看队中对象如下图只有178646个,所以对象未逃逸的情况下是存在栈上分配的。
关闭逃逸分析后执行,通过HSDB查看堆上有1000000个AllocationOnStack对象。
1.2.1.4.2.3 标量替换
标量:不可再分,java中的基本数据类型就是标量
聚合量:可再分,对象
public class Demo1 {
public static void main(String[] args) {
Demo1_A demo1_a = new Demo1_A(1,2);
System.out.println(demo1_a.a);// 编译时标量替换直接将demo1_a.a替换成1
System.out.println(demo1_a.b);// 编译时标量替换直接将demo1_a.b替换成2
}
}
class Demo1_A {
public int a;
public int b;
public Demo1_A(int a, int b) {
this.a = a;
this.b = b;
}
}
以上代码因为demo1_a未发生逃逸,所以编译时会直接将demo1_a.a替换成1,直接将demo1_a.b替换成2。
1.2.1.4.2.4 锁消除
public class Demo2 {
public void test () {
Demo2 demo2 = new Demo2();
synchronized (demo2) {
// 销除同步代码块
System.out.println("锁销除");
}
}
}
以上代码因为demo2并未发生逃逸所以编译时将同步代码块销除
1.2.1.5 热点代码缓存区
热点代码经过即时编译后的硬编码需要缓存起来,即热点代码缓存区,存放在方法区。
可通过下面命令查看缓存区大小:
java -client -XX:+PrintFlagsFinal -version | grep CodeCache
1.3 如何理解java时半编译半解释型语言
1.javac编译,java命令执行
2.字节码解释器解释执行,模板解释器编译执行
2 运行模式
2.1 三种运行模式
因为jvm有两种解释,所以有三种运行模式:
-Xint 纯字节码解释器
-Xcomp 纯模板解释器
-Xmixed 字节码解释器+模板解释器
默认时混合模式。
当虛拟机启动的时候,字节码解释器可以首先发挥作用,而不必等待即时编译器全部编译完成再执行,这样可以省去许多不必要的编译时间;随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,以换取更高的程序执行效率。
2.1 触发即时编译的条件
什么情况下才会促发即时编译呢?
一段时间内,Client 编译器模式下, 连续执行 1500次促发即时编译;Server 编译器模式下,连续执行 10000次促发即时编译。
可通过下面命令查看:
java -client -XX:+PrintFlagsFinal -version | grep CompileThreshold
2.2 热度衰减
当超过一定的时间限度(这个一定时间就是半衰周期), 如果程序的调用次数仍然不足以让它提交给即时编译器编译,那这个程序的调用计数器就会被减少一半,这个过程称为热度的衰减 。可以通过-XX:-UseCounterDecay命令关闭热度衰减。可通过-XX:CounterHalfLifeTime指定半衰周期。
2.3 即时编译如何运行
触发即时编译后不是马上执行,jvm是把这个即时编译任务加入队列,VM_THREAD从这个队列中读取任务,并运行。
查看执行即时编译的线程:
java -client -XX:+PrintFlagsFinal -version | grep CICompilerCount
可以通过-XX:CICompilerCount=N 设置。
参考:
https://www.cnblogs.com/yanl55555/p/13334713.html?utm_source=tuicool
https://www.cnblogs.com/grasp/archive/2004/01/13/10184383.html