JIT和codeCache

jvm解释执行 和 jit(即时编译技术)

解释执行:java代码-javac->字节码-interpreter->解释成机器码执行
jit(即时编译):java代码-javac->字节码-JITcompile->机器指令并存储
说明:单次执行上jit会更加耗时,所以此时使用简单的解释执行,但是如果某段代码被多次重复执行,jit编译后的机器指令可以直接使用,而解释执行则需要每次都将字节码解释成机器码来执行,所以此时jit编译会更优。

现在主流的商用虚拟机(如:Sun HotSpot、IBM J9)中几乎 都同时包含解释器和编译器,三大商用虚拟机之一的JRockit是个例外,它内部没有解释器,因此会有启动相应时间长之类的缺点,但它主要是面向服务端的应用,这类应用一般不会重点关注启动时间。

解释器与编译器二者各有优势:
当程序需要迅速启动和执行时,解释器可以首先发挥作用,省去编译的时间,立即执行;
当程序运行后,随着时间的推移,编译器逐渐会发挥作用,把越来越多的代码编译成本地代码后,可以获取更高的执行效率;
解释执行可以节约内存,而编译执行可以提升效率;

java是解释执行还是编译执行?

代码,刚开始都是被编译器编译成字节码文件,一般jvm的执行在开始的时候进行解释执行,然后jvm会判断某段字节码是否需要JIT进行编译、保存编译的结果、执行,如果需要会进行JIT编译执行,所以Java 本身是一种半编译半解释执行的语言;

场景会进行JIT编译执行呢?

下面给出几个实例:
循环代码:
JIT编译器针对循环代码可以优化:决定何时从主存取值,何时向寄存器存值。考虑下面这段代码:
清单 1. 主存 or 寄存器测试代码
public class RegisterTest {
 private int sum;
 public void calculateSum(int n) {
 for (int i = 0; i < n; ++i) {
 sum += i;
 }
 }
}
在某些时刻,sum 变量居于主存之中,但是从主存中检索值是开销很大的操作,需要多次循环才可以完成操作。正如上面的例子,如果循环的每一次都是从主存取值,性能是非常低的。相反,编译器加载一个寄存器给 sum 并赋予其初始值,利用寄存器里的值来执行循环,并将最终的结果从寄存器返回给主存。这样的优化策略则是非常高效的。但是多线程需要考虑同步的问题。

动态代码:
JIT编译器将动态代码修改固定代码
解释器遇到 b = obj1.equals(obj2) 这样一句代码,它则会查询 obj1 的类型从而得知到底运行哪一个 equals() 方法。而这个动态查询的过程从某种程度上说是很耗时的。
JVM 注意到每次运行代码时,obj1 都是 java.lang.String 这种类型,那么 JVM 生成的被编译后的代码则是直接调用 String.equals() 方法。这样代码的执行将变得非常快,因为不仅它是被编译过的,而且它会跳过查找该调用哪个方法的步骤。
如果下次执行代码时,obj1 不再是 String 类型了,JVM 将不得不再生成新的字节码。尽管如此,之后执行的过程中,还是会变的更快,因为同样会跳过查找该调用哪个方法的步骤。这种优化只会在代码被运行和观察一段时间之后发生。

JIT两种编译器

Hotspot JVM内置了2种编译器,C1编译器和、C2编译器。java -version 命令行可以直接查看当前系统使用的是 client 还是 server 模式。
C2编译器:在将代码编译成机器码之前,需要收集大量的统计信息以便在编译的时候做优化,因此编译后的代码执行效率也高,代价是程序启动速度慢,并且需要比较长的执行时间才能达到最高性能;
C1编译器:其目标在于使程序尽快进入编译执行阶段,因此编译前需要收集的统计信息比C2少很多,编译速度也快不少。代价是编译出的目标代码比C2编译的执行效率要低。C1编译的执行效率也比解释执行有巨大的优势。

JIT三种编译方式

mixed方式:默认的编译方式,开始解释执行,一段时间之后编译器在统计分析之后开始编译
纯编译方式:所有方法在第一次被调用的时候就会被编译成机器代码。加上这个参数之后,随之而来的问题是启动时间变得很长,差不多是原来的2倍还多。
开启纯编译方式的JVM参数-Xcomp
分层编译方式:一种折衷方式,在系统启动之初执行频率比较高的代码将先被C1编译器编译,以便尽快进入编译执行。随着时间推进,一些执行频率高的代码会被C2编译器再次编译,从而达到更高的性能。
开启分层编译模式的参数:-XX:+TieredCompilation 

在JDK8中,当以server模式启动时,分层编译默认开启。需要注意的是,分层编译方式只能用于server模式中,如果需要关闭分层编译,需要加上启动参数 -XX:-TieredCompilation;如果以client模式启动,-XX:+TieredCompilation 参数将会被忽略。
链接:https://juejin.im/post/5aebf997f265da0ba76f99db

针对JIT编译器的调优:
1 jvm启动模式:JVM Server 模式与 client 模式启动,最主要的差别在于:-server 模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。

2 设置jvm codeCache大小
以client模式或者是分层编译模式运行的应用,由于需要编译的类更多(C1编译器编译阈值低,更容易达到编译标准),所以更容易耗尽codeCache。当发现codeCache有不够用的迹象(通过上一节提到的监控方式)时,可以通过启动参数来调整codeCache的大小。
-XX:ReservedCodeCacheSize=256M
那具体应该设置为多大合适呢? 根据监控数据估算,例如单位时间增长量、系统最长连续运行时间等。如果没有相关统计数据,一种推荐的设置思路是设置为当前值(或者默认值)的2倍。
需要注意的是,这个codeCache的值不是越大越好。对于32位JVM,能够使用的最大内存空间为4g。这个4g的内存空间不仅包括了java堆内存,还包括JVM本身占用的内存、程序中使用的native内存(比如directBuffer)以及codeCache。如果将codeCache设置的过大,即使没有用到那么多,JVM也会为其保留这些内存空间,导致应用本身可以使用的内存减少。对于64位JVM,由于内存空间足够大,codeCache设置的过大不会对应用产生明显影响。
在JDK 8中,提供了一个启动参数 -XX:+PrintCodeCache 在JVM停止的时候打印出codeCache的使用情况。其中max_used就是在整个运行过程中codeCache的最大使用量。可以通过这个值来设置一个合理的codeCache大小,在保证应用正常运行的情况下减少内存使用。

3 调整编译阈值
4 检查编译过程
5 编译线程

借鉴:
https://www.ibm.com/developerworks/cn/java/j-lo-just-in-time/index.html

codeCache是什么?

Java代码在执行时一旦被编译器编译为机器码,下一次执行的时候就会直接执行编译后的代码,也就是说,编译后的代码被缓存了起来。缓存编译后的机器码的内存区域就是codeCache,一块独立于Java堆之外的内存区域。除了JIT编译的代码之外,Java所使用的本地方法代码(JNI)也会存在codeCache中。

怎么看codeCatch已满,满了怎么处理?
jvm日志出现如下内容:
Java HotSpot(TM) 64-Bit Server VM warning: CodeCache is full. Compiler has been disabled.
此时的后果是:
JIT编译器被停止了,并且不会被重新启动,此时会回归到解释执行;
被编译过的代码仍然以编译方式执行,但是尚未被编译的代码就 只能以解释方式执行了。

怎么解决上面的问题?
获取当前codeCache的最大: jinfo -flag ReservedCodeCacheSize pid,通常在64 bit机器上默认是48m。
设置codeCache的空间更大:
-XX:ReservedCodeCacheSize= 更大空间
启用codeCache的的回收机制:
通过在启动参数上增加:-XX:+UseCodeCacheFlushing 来启用;
打开这个选项,在JIT被关闭之前,也就是CodeCache装满之前,会在JIT关闭前做一次清理,删除一些CodeCache的代码;
如果清理后还是没有空间,那么JIT依然会关闭。这个选项默认是关闭的;

code cache调优:
以client模式或者是分层编译模式运行的应用,由于需要编译的类更多(C1编译器编译阈值低,更容易达到编译标准),所以更容易耗尽codeCache。当发现codeCache有不够用的迹象时,可以通过启动参数来调整codeCache的大小。
-XX:ReservedCodeCacheSize=256M
那具体应该设置为多大合适呢? 根据监控数据估算,例如单位时间增长量、系统最长连续运行时间等。如果没有相关统计数据,一种推荐的设置思路是设置为当前值(或者默认值)的2倍。
需要注意的是,这个codeCache的值不是越大越好。
对于32位JVM,能够使用的最大内存空间为4g。这个4g的内存空间不仅包括了java堆内存,还包括JVM本身占用的内存、程序中使用的native内存(比如directBuffer)以及codeCache。如果将codeCache设置的过大,即使没有用到那么多,JVM也会为其保留这些内存空间,导致应用本身可以使用的内存减少。
对于64位JVM,由于内存空间足够大,codeCache设置的过大不会对应用产生明显影响。
在JDK 8中,提供了一个启动参数 -XX:+PrintCodeCache 在JVM停止的时候打印出codeCache的使用情况。其中max_used就是在整个运行过程中codeCache的最大使用量。可以通过这个值来设置一个合理的codeCache大小,在保证应用正常运行的情况下减少内存使用。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值