首页 > 其他分享 >JIT即时编译器

JIT即时编译器

时间:2023-03-02 15:44:23浏览次数:35  
标签:调用 即时 代码 JIT server 编译 编译器 方法

一、概念
Java是编译与解释共存的语言,简单来说,字节码文件通过解释器进行一行一行解释执行,当虚拟机发现某个方法或代码块的运行特别频繁,就会把这些代码认定为“热点代码”(Hot Spot Code),在运行时,虚拟机将会把这些代码编译成本地机器码。因此被称为“即时编译”(即JIT),热点代码的本地机器码缓存在本地,下次执行热点代码时,可直接调用本地机器码。极大地改善了性能。
注意:这里的热点代码是指:多次调用的方法、被多次执行的循环体。
虚拟机中内同时包含解释器与编译器,解释器与编译器经常是相辅相成地配合工作。
二、JIT分类
JIT编译器有两种被称为 client 和 server。通常称这些编译器为C1(编译器1, client编译器)和C2(编译器2,server编译器)。
client 编译器
       两种编译器的最主要的差别在于编译代码的时机不同。 client编译器开启编译比server编译器要早。意味着在代码执行的开始阶段, client编译器比server编译器要快,因为
它的编译代码相比server编译器而言要多。
server 编译器
      server 编译器在编译代码时可以更好地进行优化。最终,server编译器生成的代码要比client编译器快。
1、如果应用的启动时间是首要的性能考量,那client编译器就是最有用的。
2、如果追求高质量的编译代码优化,对于长时间运行的应用来说,应该一直使用server编译器,最好配合分层编译。
三、分层编译
JVM 在启动时用 client 编译器,然后随着代码变热使用server编译器,这种技术被称为分层编译。代码先由clien编译器编译,随着代码变热,由server编译器重新编译。
通过java -version可以看到,JAVA8使用的混合模式,即分层编译。

Java8 中,分层编译默认为开启。通过 -XX:+ TieredCompilation 开启或者关闭分层编译。
四、为什么要做分层编译
读到这里,答案很明显了。由于编译器compile本地代码需要占用程序启动时间,要编译出优化程度更高的代码所花费的时间可能更长,且此时解释器还要替编译器收集性能监控信息,这对解释执行的速度也有影响。所以,为了在程序启动响应时间与运行效率之间达到最佳平衡。
编译条件与对象
五、基于计数器来编译
  编译是基于两种JVM计数器的:方法调用计数器 和 方法中的循环回边计数器。
  方法调用计数器统计的是一段时间之内方法被调用的次数。当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那该方法的调用计数器就会被减少一半,这个过程被称为方法调用计数器热度的衰减(Counter Decay),而这段时间就称为此方法统计的半衰周期(Counter Half Life Time), 使用虚拟机参数-XX:- UseCounterDecay来关闭热度衰减。这样只要系统运行时间足够长,大部分方法都会被编译成本地代码。还可以使用 -XX:CounterHalfLifeTime 参数设置半衰周期的时间,单位是秒。
  回边实际上可看作是循环完成执行的次数,所谓循环完成执行,包括达到循环自身的末尾,也包括执行了像 continue 这样的分支语句。

六、标准编译
JVM执行某个Java方法时,会检查该方法的两种计数器总数,然后判定该方法是否适合编译。如果适合,该方法就进入编译队列。这种编译通常叫标准编译。
七、栈上替换(on- Stack Replacement,OSR)
  如果循环真的很长——或因包含所有程序逻辑而永远不退出,在这种情况下,JVM不等方法被调用就会编译循环。所以循环毎完成一轮,回边计数器就会增加并被检测。如果循环的回边计数器超过阈值,那这个循环(不是整个方法)就可以被编译。这种编译称为栈上替换(on- Stack Replacement,OSR)。
八、内联
编译器所做的最重要的优化是方法内联。利用调用对象的属性或方法互相调用传参。

public class Point { private Integer x; public Integer getX() { return x; } public void setX(Integer x) { this.x = x;} }
Point p = new Point(); p.setX(p.getX()*2); //编译之后,本质上执行 Point p = new Point(); p.x = p.x * 2;
九、逃逸分析
逃逸分析的基本原理是:分析对象动态作用域,当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,这种称为方法逃逸;
甚至还有可能被外部线程访问到,譬如赋值给可以在其他线程中访问的实例变量,这种称为线程逃逸;
   JVM 会为对象实例采取不同程度的优化:栈上分配、标量替换、同步消除

标签:调用,即时,代码,JIT,server,编译,编译器,方法
From: https://www.cnblogs.com/yaoshuigebiss/p/17171831.html

相关文章