AliJVM实践
作者:莫简豪(花名:坤谷)@JianhaoMo
很多人已经知道,阿里广泛使用Java技术。由于阿里对Java使用的广泛和深入,以至于阿里开始了自己的JVM实践。业界中Twitter也有JVM的定制优化实践,但细节不太了解。2010年开始,阿里核心系统部专用计算组基于OpenJDK6 Hotspot JVM定制优化自己的JVM,经过几次双11的考验。2013年开始,改为基于OpenJDK7,并改名为阿里JVM。
JVM简介
JVM,Java虚拟机,是执行Java字节码的虚拟机。有关JVM、Java运行环境JRE和Java开发包JDK的关系可以参考这个页面(http://docs.oracle.com/javase/7/docs/)的图片,很直观。粗粗地说,Hotspot JVM解释运行Java字节码。解释运行的同时发现热点,JIT编译器将热点编译为机器码,这是在现代JVM上运行Java,性能并不差的原因。另外JVM的另一显著的功能是自动垃圾回收,GC。示意图如下:
注意到图中还有GCIH,intrinsic,JFR,这些将在下面阿里JVM中提到。
定制优化解决的问题
官方JVM是一个通用产品,一大目标是尽可能的兼容各个平台和满足大部分应用场景的需求。由于开发和维护资源有限,对于特定平台和应用场景而言,官方JVM在性能和功能上,都有取舍。而在阿里,所使用的平台是统一的x86平台,应用也有自己的场景特点。针对平台和应用场景的极致的优化,可以做得更多。另一方面,由于阿里的规模大,单机上每一点点进步,乘以规模,其效益都会被放大。所以值得投入资源来定制优化自己的JVM。上图中,阴影的部分就是阿里JVM定制优化的地方。
最新亮点
AliJVM7最新版本主要有两大更新–协程和MSC并行化。
协程的支持主要是为了解决同步IO的性能问题,在底层将IO异步化。这样Java应用既可以享受同步IO代码的良好的可读性和可维护性,还能体验到性能的大幅提升!在阿里内部中间件性能大赛中使用协程的效果如下,比kilimi库性能更好。
MSC的并行化是为了解决应用Full GC停机时间较长的问题。官方JDK的Full GC实现是单线程的,将其并行化能够缩短停机时间,从而达到提升应用性能的目的。测试结果如下。
性能优化
首先,针对Intel X86 CPU进行JVM本身的编译优化,生成性能更高的可执行代码。即图中左边静态部分。右边动态生成部分的机器码的性能和JVM本身的编译无关,所以执行性能没有影响。可以看到静态部分中,GC是一个重要的模块。举例,编译优化后young gc性能有约20%的提高。
以上是不修改源码得到的性能提升,当然也有修改源码的。例如:
x86_64平台下JNI native wrapper的分支整理优化;
去除Linux下无用的DTrace的钩子函数;
优化了GC的一些细节,包括并发的逻辑,卡表的逻辑。
为了获得更大的性能提升,阿里JVM还提供了针对特定需求的intrinsic,增加了:
CRC32、CRC32C;
byte数组比较、char数组比较;
ASCII转UTF8;
SpinLock pause
的Intrinsic方法。
这些优化大多利用了X86的SIMD指令。使用这些新的API,虽然需要应用改代码,但是带来的局部性能提升非常大。选择这些优化点,是对应用进行性能剖析得出来的。
故障排查
阿里JVM积累一些故障排查经验,并在JVM中做了优化。包括:
修复一些BUG、如GC、socket连接泄漏,并提交给社区。
在crash log里输出反汇编信息,用于帮助调查crash问题。
添加ArrayAllocationWarningSize参数,当应用申请超大数组,会打印警告,这个功能帮助排查了一些诡异的应用问题,很实用。
添加单独指定新建Java线程的栈大小的API。有些典型应用,由于某线程递归深度比较深栈溢出。调线程栈参数是全局的,对于大部分线程是浪费内存,单独指定可以解决。
添加PrintGCReason参数;将PrintJNIGCStalls、DisableExplicitGC与ExplicitGCInvokesConcurrent参数改为manageable用于帮助排查、解决GC问题。
添加forceGC()与forceCompactionGC()的API,可以来缓解CMS GC碎片化的问题,应用可以选择在没有业务请求的时间段自主触发Full GC。
使用jemalloc替代glibc的ptmalloc,解决ptmalloc物理内存不释放的bug。增加参数ReclaimMostNativeMemory以最大限度释放物理内存,当direct memory使用量达到一定值时输出预警信息并进行CMS GC,根据调用栈统计direct memory申请分布情况。
Java异常统计,对应用无侵入地统计所有异常个数以及发生次数最多的异常,用以协助诊断CPU热点,比如有应用频繁抛异常同时自己catch作为逻辑判断,对性能有较大影响。
将参数OmitStackTraceInFastThrow设为managable,如果想要排查的异常没有StackTrace,可以动态调整这个参数。
还有一个重大的定制,是加入了对Flight recorder的支持。Flight recorder源自于JRockit JVM,是一个高性能的基于事件的记录器,它可以记录下应用运行过程中JVM发生的各种行为,对于应用的问题诊断有着很大的帮助。OpenJDK Hotspot中只有Flight recorder的一些埋点,没有完整实现。阿里JVM基于OpenJDK Hotspot实现了自己的Flight recorder,即图中JFR部分。记录的事件包括:GC、线程同步、JIT、类加载和VMOperation。
对于Flight recorder的问题诊断功能,可以通过阿里内部JVM故障排查系统进行使用。同时JVM故障排查系统还提供了GC、线程dump、Crash 等日志的分析诊断功能。
最后,阿里JVM提供了一个JIT符号信息的接口,结合阿里的系统级性能剖析系统(ASWP),和阿里监控平台,可以在线对线上应用低开销地进行系统级性能剖析和监控。
解决特定应用痛点
特定应用有GC相关痛点,我们针对这种应用,开发了GC Invisible Heap,GC不可见堆,即图中GCIH部分。有应用有在同一机器多JVM之间共享对象的需求,有的应用有将超大文件,映射到对象的需求。业界有个Off Heap的概念,都是JVM之上的实现,基本上避免不了序列化和反序列化。GCIH修改了JVM,直接映射Java对象,没有序列化开销。解决也应用的痛点。目前共享对象应用已经在线上稳定运行多时,超大堆压缩功能正在研发。
故障发现排查服务化
阿里JVM的实践,除了性能优化,和定制化。很大的投入是在故障排查方面。而故障排查很多时候还需要人工经验,把之前积累的经验,做成服务化系统。一方面解放阿里JVM团队,去做更多研发工作,另一方面也为将来服务更多的客户,打好基础。如下图: