音乐无国界,但是音乐人有国界。
云原生亦如此。虽没有限定的编程语言,但应用所使用的编程语言已经决定了应用部署运行的行为。
Java诞生于20年前,拥有大量优秀的企业级框架,践行OOP理念,更多体现的是严谨以及在长时间运行条件下的稳定性和高性能。反观如今,在要求快速迭代交付的云场景下,语言的简单性似乎成了首要的要求,而传统的Java语言显得有一些过于重量了。
本文由阿里巴巴JVM团队技术专家郁磊(花名:梁希)分享JVM团队是如何面对和处理集团巨大的业务规模和复杂的业务场景的。
ElasticHeap
Java常因为耗资源而受诟病,其中最显著一点就是Heap对内存的占用,即便没有请求在处理也没有对象分配,进程仍然会保留完整的堆内存空间,保障GC进行分配内存和操作内存的快速敏捷。
AJDKZenGC/ElasticHeap双十一全面支持核心链路上百应用和数十万实例。
JDK12开始支持固定时间的触发concurrentmark并在remark中收缩Java堆归还内存的功能,然而并未解决在stw中增加暂停时间的问题,因此无法在每次youngGC时做内存归还。ElasticHeap在并发异步线程中完成内存处理反复map/unmap以及pagefault的开销,因此任意一次youngGC都可以敏捷的及时归还内存,或重新恢复内存使用。
ElasticHeap阿里巴巴实战
ElasticHeap场景1:可预测的流量高峰重塑云上的Java语言
ElasticHeap场景2:单机运行多个Java实例
多个Java实例接受的流量任务较为随机,峰值不会重叠,在闲时可以有效降低多个实例整体的内存占用,提高部署密度。
双11验证核心交易系统使用ElasticHeap进行低功耗模式运行,大幅降低WSS(WorkingSetSize)规模的实例。
静态编译
很多云上的新应用不约而同地选择了Go语言,很大的原因是Go应用对运行时没有依赖,静态编译的程序启动速度快,也不需要通过JIT来预热。在阿里有大量Java代码的前提下,我们是如何为Java注入这方面的能力的呢?
Java静态编译技术是一种激进的AOT技术,通过单独的编译阶段将Java程序编译为本地代码,在运行时无需传统Java虚拟机和运行时环境,只需操作系统类库支持即可。其工作基本原理如下图所示。静态编译技术实现了Java语言与原生native程序的“合体”,将原本的Java程序编译成为了一个自举的具有Java行为的原生native程序,由此兼有Java程序和原生native程序的优点。
JVM团队与SOFAStack团队密切合作,在中间件应用上率先实现静态编译的落地。将一个应用的启动速度从60秒优化到3.8秒,双十一期间静态编译的应用运行稳定,没有故障,GC停顿时间在100毫秒,在业务允许范围之内,内存占用和RT与传统Java应用持平。
综上所述,静态编译的应用在稳定性、资源占用、RT响应等各方面指标与传统Java应用基本持平的状况下,将启动时间降低了2000%。
Wisp2
当你用时下最酷炫的Vert.X开发一个简单的Web服务,准备体验一下最强的性能,QA同学拿来一台1C2G的容器让你压一下,你却发现你怎么也拼不过别人Go应用。研究之后发现,原来协程模型在这样的少核心的情况下性能要好很多。是时代变了,Java落伍了?
AJDKWisp2回答了这个问题:Java同样可以拥有高性能的协程。今年是Wisp2大规模上线的第一年,Wisp2具有如下特点:
在整个Javaruntime中支持了协程调度,线程(比如Socket.getInputStream().read())阻塞会变成更轻量的协程切换。完全兼容ThreadAPI,在开启Wisp2的JDK中,Thread.start()实际创建的是一个协程(轻量级线程),可以类比Go只提供协程关键字go而没有暴露线程接口;我们同样只提供创建协程的方式,应用可以透明切换到协程。支持workstealing,调度策略特别适合web场景,在高压力下调度开销极小。在今年双十一,Wisp支持了上百应用,十万级容器,其中90%的容器已经升级到Wisp2。
我们可以看到峰值附近,Wisp2机器的CPU要低7%(Wisp1更低,Wisp2的取向是RT,因此CPU会高一些)左右,这主要是轻量级调度所节省的sysCPU。0点的CPU是相等的,这也说明一点:Wisp2解决的是调度开销,当CPU低,调度没有压力时是看不出差距的。
从RT角度看,Wisp2机器的RT要低20%左右,RT减少明显的一个原因是这批机器的CPU压力很大,协程的调度优势更容易体现出来。这样的优势可以帮助系统摸高到更高的水位,整体地提高利用率而担心RT过高导致系统雪崩。
FDO
双十一正零点相对后面几分钟会有一个明显的CPU峰值,根据数据分析,主要原因是双十一零点触发了JIT编译。举个例子,程序里有逻辑:
if(is1111(LocalDate.now())){branch1}else{branch2}
假设预热时一直在走branch2,那么JIT有理由相信后续基本也都会走branch2,而不会对branch1编译。在零点时,我们进入branch1,此时就需要触发退优化重新编译方法。我们来看AJDK如何通过profiling解决这个问题。
退优化原理及其危害
JDK运行代码的时候,采用分层编译的方式对Java方法进行动态编译。在最高等级(峰值性能最好)的编译中,出于性能的考虑,编译的时候会根据收集的信息做一些比较乐观的假设,一旦这些假设条件不满足了,就会出现退优化的现象。比如某个热点方法中某段代码仅会在双十一中执行,那么在预热过程中这段代码不会被编译,双十一到来时这段代码一旦被执行,就会触发整个方法的退优化。
发生退优化有两个方面的负面影响,一是需要运行的方法由高效率的编译执行变成了解释执行,运行速度降低百倍以上;二是流量高峰期退优化的方法会很快被重新编译,编译线程会消耗CPU。因此在双十一这种流量短时间剧增且与预热流量不太一样的场景下,退优化的危害会特别明显。
通过FDO减少退优化
FDO是feedbackdirectedoptimization的缩写,即参考以往JVM运行时的编译信息,指导本次运行时进行更好的编译。具体的,我们采用了两个层面的方法来减少退优化。
将每次运行时的退优化信息记录到文件中,下次运行时读取这个文件,在决定是否做乐观假设的时候参考文件中的信息做判断,从而减少退优化的概率。信息显示出现最多的退优化与if-else相关,占总数量的一半以上。我们提供了一个方法根据以往出现if-else退优化的信息,关闭某个路径上所有相关的乐观假设。
双十一中FDO的效果
FDO今年双十一上线,目标解决两个问题:
1、双十一0点流量高峰和退优化/编译高峰叠加造成的CPU使用率脉冲过高。
2、预热效率低,压测经过前长时间预热后,增大流量时仍然伴随着大量的编译及退优化。
针对第一个问题,我们收集了双十一高峰第一分钟的退优化/C2编译次数以及CPU数据。
可见开启FDO后高峰期C2编译数目减少约45%,退优化数目减少约70%。
CPU数据上,高峰期第一分钟内开启FDO后CPU由约67.5降低到63.1,降低约7.0%。
第二个目标可以通过压测第一分钟的CPU数据验证。
开启FDO,压测第一分钟CPU使用率由66.19降低到60.33%,降低约10%。
Grace
ZProfiler一直是全集团排查Java应用各类问题的利器,而Grace作为其平台化的版本,对其实施了一系列的优化,从原来的单机版本到现在的Master/Worker架构,同时引入了任务排队机制,在高压力情况下对用户的任务进行排队从而解决Worker不堪重负的问题。在可维护性、拓展性、以及用户体验上得到了质的提升,为后续工具平台的上云、开源事项打下了夯实的基础。
目前已经集成了HeapDump功能,在继承ZProfiler功能的基础上做了一定的优化,提升了解析引擎的版本,支持更全面的OQL语法等等。
JDK11
JDK8作为一个经典版本,正被大规模使用,虽然从JDK6和7迁移上来有一定的阵痛,但是升级后普遍的反馈是:“真香”。
OpenJDK8的下一个稳定版本是OpenJDK11。JVM团队自然会在这个方向上积极跟进,目前AJDK11支持了AJDK8的Wisp2、多租户特性。本次双十一的部分集群已经上线到JDK11,表现稳定。
升级JDK11是否会和升级JDK8一样给我们带来同样的的惊喜呢?在JDK11上我们可以体验到最新的ZGC。
ZGC
JDK11引入了一个重要特性:ZGC内存垃圾回收器。这个垃圾回收器号称能够在几十GB至若干TB的堆上把暂停时间保持在10ms以内。许多Java开发者苦于过去的垃圾回收器的暂停时间带来延迟,ZGC短暂停的特性未来无疑会成为Java开发者的新宠。
目前ZGC在OpenJDK中仍然处于实验特性,而且JDK11尚未在产业界完全普及,JDK11只支持Linux上的ZGC(MacOS和Windows的ZGC预计在2020年3月发布的JDK14版本才会支持),许多Java开发者仍然只能垂涎欲滴,处于观望状态。
向来敢于吃螃蟹的我们岂能望而却步?阿里JVM团队和数据库团队已经开始让数据库应用运行在ZGC上,并根据运行的效果对ZGC进行了相应的改进工作,包括ZGC的页缓存机制优化、ZGC的触发时机优化等等。
从9月开始,两个团队推动线上数据库应用在ZGC上运行,目前已经稳定运行两个月,并顺利通过双十一大考。线上反馈的效果可喜可贺:
1、JVM暂停时间保持在官方的10ms以内;
2、ZGC大大改善了线上运行集群的平均RT与毛刺指标。
小结
从上述的功能特性可以看到AJDK已经从一个传统的ManagedRuntime脱胎换骨。今后AJDK将继续致力于提高云上的应用的开发体验,通过底层的创新为上层应用提供更多的可能。
在Dragonwell上使用AJDK功能上述的这些经过双十一考验的功能都将随着Dragonwell陆续开源和交付到广大用户手中,敬请关注。
AlibabaDragonwell8是一款免费的OpenJDK发行版。它提供长期支持,包括性能增强和安全修复。AlibabaDragonwell8目前支持X86-64/Linux平台,在数据中心大规模Java应用部署情况下,可以大幅度提高稳定性、效率以及性能。AlibabaDragonwell8是OpenJDK的下游(friendlyfork),使用了和OpenJDK一样的license。AlibabaDragonwell8与JavaSE标准兼容,用户可以使用AlibabaDragonwell8开发和运行Java应用程序。此次开源的AlibabaDragonwell8是阿里巴巴内部OpenJDK定制版AJDK的开源版本,AJDK为在线电商,金融,物流做了结合业务场景的优化,运行在超大规模的,1,000,000+服务器的阿里巴巴数据中心。
近期我们正在紧密筹备AlibabaDragonwell11的release。Dragonwell11是基于OpenJDK11的Dragonwell发行版本,拥有更多特性,可以更多地为云上场景赋能,模块化更加清晰,并将获得长期的支持,因此推荐大家关注和适时升级。