性能文章>相同版本JVM和Java应用,在X86和AArch64平台性能相差30%,何故?>

相同版本JVM和Java应用,在X86和AArch64平台性能相差30%,何故?原创

197804

编者按:目前许多公司同时使用X86和Aarch642种主流的服务器。但是相同版本的JVM和Java应用,相同的参数JVM参数,但是应用性能在不同的平台中表现相差30%,X86远好于Aarch64平台。本文分析了一个应用在Aarch64性能下降的例子,发现JVM的CodeCache大小是引起这个性能问题的根源,进而研究什么导致了不同平台上Codecache大小的不同。最后笔者给出了不同平台中该如何设置参数规避该问题。希望本文能给读者一些启示:当使用不同的硬件平台时需要关注底层硬件对于上层应用的影响。

业务在x86和arrach64上同时部署时(相同的JDK和Java应用版本),发现aarch64平台性能下降严重问题。进一步查看日志,发现在aarch64平台中偶有如下情况:
image.png
这代表JVM中的CodeCache满了,导致编译停止,未编译的方法只能解释执行,进而严重影响应用性能。什么是CodeCache?

CodeCache是什么

简单来说,CodeCache用于存放编译后的方法,主要分为三部分:
1.Non-nmethods:包括运行时Stub,Adapter等。
2.Profiled nmethod:包括会采集信息的方法,即分层编译中第2、3层的方法
3.Non-Profiled nmethods:包括不采集信息的方法,即分层编译中第1、4层的方法,也包括JNI的方法
注:分层编译指的是JVM同时存在C1和C2两种编译器,C1做一些简单的编译优化,耗时较短,C2做更多复杂的编译优化,性能较好,编译耗时较多。分层编译的触发在JVM内存会根据相应的条件进行触发,关于更多分层编译相关知识可以参考官网。
在JDK9之后[1],这些会分配到不同的区域(使用不同区域的优点:查找、回收等),JDK8中会分配到同一块区域。
JVM平时会清理一些不可达的方法,例如由于退优化等产生的死方法,另外UseCodeCacheFlushing选项(默认开启),还会清理较老以及执行较少的方法。一旦CodeCache满了之后,会停止编译,直到CodeCache有空间,若关闭了UseCodeCacheFlushing选项,则会直接永久停止编译。
不同的JVM版本以及不同的参数,默认的CodeCache大小不同。JDK11中默认参数下大小为240M,若想获取(确认)默认情况下的CodeCache大小,建议使用-XX:+PrintFlagsFinal选项获取ReservedCodeCache的大小。
CodeCache大小主要通过以下选项调节:

Option Description
InitialCodeCacheSize 初始的CodeCache大小(单位字节)
ReservedCodeCacheSize 预留的CodeCache大小,即最大CodeCache大小(单位字节)
CodeCacheExpansionSize CodeCache每次扩展大小(单位字节)

使用–XX:+PrintCodeCache选项可以打印应用使用的CodeCache情况,如下:
image.png
其中max_used表示应用中使用到的CodeCache大小,据此可以设置合适的ReservedCodeCacheSize值。

AArch64 vs x86_64

我们都知道aarch64和x86分别为RISC和CISC架构,因此代码密度方面存在一定差异,在这篇文章[2]中比较了不同指令集下手写汇编的大小,可以看到aarch64的代码密度是RISC架构中较优的,但相比x86_64仍稍差些(其中RISC最差,m68k最好)。

image.png
另外笔者选用业界通用的java测试套dacapo[3]比较aarch64和x86_64下codecache占用的大小。
image.png
可以看到,在aarch64架构下,CodeCache均比x86_64要大,但根据不同场景,大小差距不同,在5%-20%之间。因此在我们发现相同应用在x86和aarch64上时,CodeCache大小需要进行相应的调节。
除此之外,还需要注意InlineSmallCode选项,JVM只会inline代码体积比该值小的方法。JVM通过inline可以触发更多的优化,因此inline对于性能提升也很重要。在JDK11中,InlineSmallCode在x86下的默认值为2000字节,在aarch64下的默认值为2500字节。而JDK8中,InlineSmallCode在x86和aarch64下默认值均为2000字节。因此建议迁移时也相应修改InlineSmallCode的值。

[1] https://bugs.openjdk.java.net/browse/JDK-8015774
[2] http://web.eece.maine.edu/~vweaver/papers/iccd09/ll_document.pdf
[3] http://dacapobench.org/

后记

如果遇到相关技术问题(包括不限于毕昇JDK),可以进入毕昇JDK社区查找相关资源(点击原文进入官网),包括二进制下载、代码仓库、使用教学、安装、学习资料等。毕昇JDK社区每双周周二举行技术例会,同时有一个技术交流群讨论GCC、LLVM、JDK和V8等相关编译技术,感兴趣的同学可以添加如下微信小助手,回复Compiler入群。
image.png

分类:
标签:
请先登录,再评论

暂无回复,快来写下第一个回复吧~

为你推荐

不起眼,但是足以让你有收获的JVM内存分析案例
分析 这个问题说白了,就是说有些int[]对象不知道是哪里来的,于是我拿他的例子跑了跑,好像还真有这么回事。点该 dump 文件详情,查看相关的 int[] 数组,点该对象的“被引用对象”,发现所
从一起GC血案谈到反射原理
前言 首先回答一下提问者的问题。这主要是由于存在大量反射而产生的临时类加载器和 ASM 临时生成的类,这些类会被保留在 Metaspace,一旦 Metaspace 即将满的时候,就会触发 Fu
关于内存溢出,咱再聊点有意思的?
概述 上篇文章讲了JVM在GC上的一个设计缺陷,揪出一个导致GC慢慢变长的JVM设计缺陷,可能有不少人还是没怎么看明白的,今天准备讲的大家应该都很容易看明白 本文其实很犹豫写不写,因为感觉没有
协助美团kafka团队定位到的一个JVM Crash问题
概述 有挺长一段时间没写技术文章了,正好这两天美团kafka团队有位小伙伴加了我微信,然后咨询了一个JVM crash的问题,大家对crash的问题都比较无奈,因为没有现场,信息量不多,碰到这类问题我
又发现一个导致JVM物理内存消耗大的Bug(已提交Patch)
概述 最近我们公司在帮一个客户查一个JVM的问题(JDK1.8.0_191-b12),发现一个系统老是被OS Kill掉,是内存泄露导致的。在查的过程中,阴差阳错地发现了JVM另外的一个Bug。这个B
JVM实战:优化我的IDEA GC
IDEA是个好东西,可以说是地球上最好的Java开发工具,但是偶尔也会卡顿,仔细想想IDEA也是Java开发的,会不会和GC有关,于是就有了接下来对IDEA的GC进行调优 IDEA默认JVM参数: -
不起眼,但是足以让你收获的JVM内存案例
今天的这个案例我觉得应该会让你涨姿势吧,不管你对JVM有多熟悉,看到这篇文章,应该还是会有点小惊讶的,不过我觉得这个案例我分享出来,是想表达不管多么奇怪的现象请一定要追究下去,会让你慢慢变得强大起来,
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得