CodeCache

一、简介

JVM生成的native code存放的内存空间称之为Code Cache;JIT(即时编译器)编译、JNI(Java本地接口)等都会编译代码到native code,其中JIT生成的native code占用了Code Cache的绝大部分空间,codecache是一块独立于Java堆之外的内存区域。

JIT:Java程序最初是仅仅通过解释器解释执行的,即对字节码逐条解释执行,这种方式的执行速度相对会比较慢,尤其当某个方法或代码块运行的特别频繁时,这种方式的执行效率就显得很低。于是后来 在虚拟机中引入了JIT编译器(即时编译器),当虚拟机发现某个方法或代码块运行特别频繁时,达到某个阈值,就会把这些代码认定为“Hot Spot Code”(热点代码),为了提高热点代码的执行效率,在运行时,虚拟机将会把这些代码编译成与本地平台相关的机器码,并进行各层次的优化,完成这项任务的正是JIT编译器。

二、作用

codecache用来将热点代码缓存起来,加快代码运行速度,服务启动之后,随着时间的推移,肯定会有越来越多的方法被JIT编译成本地机器码,并存放到Code Cache,由于Code Cache大小是固定的,那么就存在被用完的风险。一旦Code Cache被填满,就会出现下面情况:

  • JVM的JIT功能会被停止,将不会编译任何额外的代码。

  • 被编译过的代码仍然以编译方式执行,但是尚未被编译的代码只能以解释方式执行。

这种情况下,如果应用中还有很多代码以解释方式执行,其性能会大大降低。

在Speculative flushing开启的情况下,当Code Cache不足时:

  • 最早被编译的一半方法将会被放到一个old列表中等待回收;

  • 在一定时间间隔内,如果old列表中方法没有被调用,这个方法就会被从Code Cache清除;

很不幸的是,在JDK1.7中,Speculative flushing释放了一部分空间,但是从编译日志来看,JIT并没有恢复正常,并且系统整体性能下降很多,出现了大量超时。

在Oracle官网上看到这样一个Bug:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8006952 由于算法问题,当Code Cache不足之后会导致编译线程无法继续,并且消耗大量CPU,导致系统运行变慢。

三、相关参数

# 查看JVM设置的codecache最大大小
jinfo -flag ReservedCodeCacheSize {pid}
# 或者
java -XX:+PrintFlagsFinal|grep --color CodeCache

# JVM启动参数
# 设置初始CodeCache大小
-XX:InitialCodeCacheSize

# 设置Reserved code cache的最大大小,通常默认是240M
-XX:ReservedCodeCacheSize=240M

# 是否在code cache满的时候先尝试清理一下,如果还是不够用再关闭编译,默认开启
-XX:+UseCodeCacheFlushing

# 关闭分层编译
-XX:-TieredCompilation

如果需要在运行时查看codecache大小,需要写段代码使用jmx查看,具体代码可以在最后的参考里面查看

四、codecache回收策略

如果满足以下条件之一则进行回收:

1)codecache满了;

2)从上次扫描到现在,存在codecache最大值1%的“状态变更的代码”;

状态变更?

  • alive -> not_entrant(存活->新进入)

  • not_entrant -> zombie(新进入->僵尸)

  • zombie -> marked_for_reclamation(僵尸->被标记为回收)

3)从上次扫描到一段时间内,codecache剩余的越多,越不容易触发回收。

回收会清理掉所有僵尸代码。如果 UseCodeCacheFlushing 打开的话还会清理非热点的存活代码。

非热点的存活代码?

  • 在每个safepoint每个活动的栈帧的热度值被置为默认值【2 * (codecache大小 / 1MB)】

  • 每次清理会将热度值-1

  • 当热度值小于一个阈值的时候会被判断为“非热点的存活代码”进而被清理,这个阈值和codecache的剩余百分比以及参数 -XX:NmethodSweepActivity 有关(默认值10),NmethodSweepActivity越大,codecache剩余百分比越小,那就越会被回收。

总结:codecache的清理时机和清理大小是在运行时动态决定的,影响因素包括:codecache总大小、剩余百分比,僵尸代码的数量,非热点存活代码的数量以及JVM的参数等。

参考:

https://juejin.cn/post/6844903601786060808

https://blog.csdn.net/Rylan11/article/details/81606119

https://www.jianshu.com/p/4214df5df334

https://blog.csdn.net/weixin_41947378/article/details/113919808

https://stackoverflow.com/questions/38173592/oracle-jvm-8-when-codecache-flushing-is-enabled-how-much-is-flushed

https://titanwolf.org/Network/Articles/Article?AID=7b7b5ba8-204b-4f76-87f1-33d94c0dfa0c#gsc.tab=0

https://blog.csdn.net/bear_lam/article/details/79400657

https://www.jianshu.com/p/b9a5cbef12f2

http://zhongmingmao.me/2019/01/02/jvm-advanced-jit/

https://tech.meituan.com/2020/10/22/java-jit-practice-in-meituan.html

https://www.codenong.com/cs107067700/

Last updated