相同版本 JVM 和 Java 应用,在 x86 和AArch64 平台性能相差30%,何故?
扫描二维码
随时随地手机看文章
编者按:目前许多公司同时使用 x86 和 AArch64 2 种主流的服务器。这两种环境的算力相当,内存相同的情况下:相同版本的 JVM 和 Java 应用,相同的 JVM 参数,应用性能在不同的平台中表现相差 30%,x86 远好于 AArch64 平台。本文分析了一个应用在 AArch64 平台上性能下降的例子,发现 JVM 的 CodeCache 大小是引起这个性能问题的根源,进而研究什么导致了不同平台上 CodeCache 大小的不同。最后笔者给出了不同平台中该如何设置参数规避该问题。希望本文能给读者一些启示:当使用不同的硬件平台时需要关注底层硬件对于上层应用的影响。业务在 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 的方法。
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 的最佳编译效果。