没有仔细分析。
业务在 x86 和 AArch64 上同时部署时(相同的 JDK 和 Java 应用版本),发现 AArch64 平台性能下降严重问题。进一步查看日志,发现在 AArch64 平台中偶有如下情况:
这代表 JVM 中的 CodeCache
满了,导致编译停止,未编译的方法只能解释执行,进而严重影响应用性能。那什么是 CodeCache
?
CodeCache 是什么
简单来说,CodeCache
用于存放编译后的方法,主要分为三部分:
-
Non-nmethods
:包括运行时 Stub,Adapter 等; -
Profiled nmethod
:包括会采集信息的方法,即分层编译中第 2、3 层的方法; -
Non-Profiled nmethods
:包括不采集信息的方法,即分层编译中第 1、4 层的方法,也包括 JNI 的方法。
注:分层编译指的是 JVM 同时存在 C1 和 C2 两种编译器,C1 做一些简单的编译优化,耗时较短,C2 做更多复杂的编译优化,性能较好,编译耗时较多。分层编译的触发在 JVM 内会根据相应的条件进行触发,关于更多分层编译相关知识可以参考相关资料 [1]。
在 JDK 9 之后 [2],这些会分配到不同的区域(使用不同区域的优点:查找、回收等),JDK 8 中会分配到同一块区域。
JVM 平时会清理一些不可达的方法,例如由于退优化等产生的死方法,另外 UseCodeCacheFlushing
选项(默认开启),还会清理较老以及执行较少的方法。一旦 CodeCache
满了之后,会停止编译,直到 CodeCache
有空间,若关闭了 UseCodeCacheFlushing
选项,则会直接永久停止编译。
不同的 JVM 版本以及不同的参数,默认的 CodeCache
大小不同。JDK 11 中默认参数下 CodeCache
大小为 240M,若想获取(确认)默认情况下的 CodeCache
大小,建议使用 - XX:+PrintFlagsFinal
选项获取 ReservedCodeCache
的大小。
CodeCache
大小主要通过以下选项调节:
Option | Description |
InitialCodeCacheSize | 初始的 CodeCache 大小(单位字节) |
ReservedCodeCacheSize | 预留的 CodeCache 大小,即最大CodeCache 大小(单位字节) |
CodeCacheExpansionSize | CodeCache 每次扩展大小(单位字节) |
使用–XX:+PrintCodeCache
选项可以打印应用使用的 CodeCache
情况,如下:
其中 max_used
表示应用中使用到的 CodeCache
大小,据此可以设置合适的 ReservedCodeCacheSize
值。
AArch64 vs x86_64
我们都知道 AArch64 和 x86 分别为 RISC 和 CISC 架构,因此代码密度方面存在一定差异,在这篇文章 [3] 中比较了不同指令集下手写汇编的大小,可以看到 AArch64 的代码密度是 RISC 架构中较优的,但相比 x86_64 仍稍差些(其中 RISC 最差,m68k 最好)。
另外笔者选用业界通用的 java 测试套 dacapo[4] 比较 AArch64 和 x86_64 下 CodeCache
占用的大小。
可以看到,在 AArch64 架构下,CodeCache
均比 x86_64 要大,但根据不同场景,大小差距不同,在 5%-20% 之间。因此在我们发现相同应用在 x86 和 AArch64 上时,CodeCache
大小需要进行相应的调节。
除此之外,还需要注意 InlineSmallCode
选项,JVM 只会 inline
代码体积比该值小的方法。JVM 通过 inline
可以触发更多的优化,因此 inline
对于性能提升也很重要。在 JDK 11 中,InlineSmallCode
在 x86 下的默认值为 2000 字节,在 AArch64 下的默认值为 2500 字节。而 JDK 8 中,InlineSmallCode
在 x86 和 AArch64 下默认值均为 2000 字节。因此建议迁移时也相应修改 InlineSmallCode
的值。业务通过对 CodeCache
相关参数的调整,达到助力 JIT 的最佳编译效果。
后记
如果遇到相关技术问题(包括不限于毕昇 JDK),可以进入毕昇 JDK 社区查找相关资源),包括二进制下载、代码仓库、使用教学、安装、学习资料等。毕昇 JDK 社区每双周周二举行技术例会,同时有一个技术交流群讨论 GCC、LLVM、JDK 和 V8 等相关编译技术,