1. 首页
  2. jvm实践

全面对比5大GC的内存伸缩能力

在软件开发中,很明显,与大型应用程序相比,小而灵活的微服务可以提供更多的优势。而JDK9的Jigsaw更加有助于分解我们的Java应用程序,从而构建更适合云原生的应用程序和微服务。而随着服务的用户越来越多,我们的应用程序需要水平扩容。在这个扩容过程中,其在单个容器中的预配置资源消耗将复制到每个实例。如此一来,正确配置垃圾回收器,这个Java程序最基础的组件,将会很大程度的影响整个项目对资源的利用率。

本文对比测试垃圾回收器的内存伸缩能力,总计覆盖5大GC:

  • G1
  • Parallel
  • ConcMarkSweep (CMS)
  • Serial
  • Shenandoah

为了测试需要,准备了一段示例代码,github地址为: https://github.com/jelastic/java-vertical-scaling-test。代码非常简单,只有3个java类:

  1. MemoryUsage.java:每隔3秒利用MemoryUsage输出used/committed/max内存。
  2. Load.java:不断分配内存,且每分配100次会sleep一段时间。
  3. Test.java:不断调用Load.java分配内存。

测试过程中配置的JVM参数如下:

  java -XX:+Use[gc_name]GC -Xmx2g -Xms32m -jar app.jar [sleep]

说明:

  • gc_name:表示指定的具体垃圾回收器;
  • Xms:初始化内存大小;
  • Xmx:内存使用上限;
  • sleep:即控制Load.java的sleep时间;

G1GC

我们首先要测试的就是G1,从JDK9以后,G1已经取代ParallelGC成为默认的GC。如果想在低版本JDK中使用G1,那么需要通过参数-XX:+UseG1GC显示指定GC。

G1最大的优势就是能压缩空闲内存,而且不需要很长的停顿时间。并且能回收不再使用的内存。经过测试发现,G1也是这方面表现最好的垃圾回收器。

现在,我们对G1进行3次测试,测试的sleep分别为0,10,100。即表示内存分配速度由快到慢。

  • 快速分配内存

对应的JVM参数如下,sleep为0,即持续不断的分配内存:

  java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 0

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_1.png

g1 memory

由图可知,发生FGC后,Reserved和Used内存非常及时的下降到最低水平。所以:G1的表现非常好,不再使用的内存归还给操作系统的速度非常快

  • 中速分配内存

对应的JVM参数如下,sleep为10:

  java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 10

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_2.png

g1 memory

  • 慢速分配内存

对应的JVM参数如下,sleep为100:

  java -XX:+UseG1GC -Xmx2g -Xms32m -jar app.jar 100

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_3.png

g1 memory

由图可知,Reserved内存和Used内存几乎完全同步,因为可以得出结论:G1并不会过多的申请内存。

  • AggressiveHeap

再做一个测试,配置JVM参数-XX:+AggressiveHeap,或者认为Xmx和Xms一样大,对应的JVM参数如下:

  java -XX:+UseG1GC -Xmx2g -Xms2g
    或者
    java -XX:+UseG1GC -Xmx2g -XX:+AggressiveHeap

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_4.png

g1 memory

由图可知,Reserved内存一直和Xmx一样大,不会有任何改变,即使你的程序可能只需要几十M甚至更少的内存,JVM也不会把那些未使用的可用内存归还给操作系统。如此一来,其他程序就无法使用这些这些内存。

所以,如果想要你的应用程序能弹性使用内存,确保没有配置-XX:+AggressiveHeap,或者Xms小于Xmx。

ParallelGC

ParallelGC以高吞吐量为主要目标,我们配置如下JVM参数:

  java -XX:+UseParallelGC -Xmx2g -Xms32m -jar app.jar 10

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_5.png

parallel memory

由图可知,FGC后未使用的内存没有被归还给操作系统(Reserved内存曲线并没有下降)。配置了ParallelGC的JVM会一直持有这些内存,除非发生了重启行为。所以,ParallelGC是不具备伸缩能力的垃圾回收器

Serial&CMS

Serial和CMS都是可伸缩的垃圾回收器。但是效果与G1相比有一定的差距,它们需要4次FGC周期才能释放所有不再使用的内存。我们配置如下JVM参数:

  java -XX:+UseSerialGC -Xmx2g -Xms32m -jar app.jar 10

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_6.png

serial memory

配置CMS也是类似的效果,JVM参数如下:

  java -XX:+UseConcMarkSweepGC -Xmx2g -Xms32m -jar app.jar 10

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_7.png

cms memory

说明:从JDK9开始,不再使用的内存可以通过JVM参数-XX:-ShrinkHeapInSteps,在第一次FGC后加速释放。

ShenandoahGC

ShenandoahGC是JDK12推出的基于Region设计的全新垃圾回收器。它非常大的不同就是:ShenandoahGC不需要FGC就能异步回收不再使用的内存并归还给操作系统。Shenandoah在探测到可用内存后,几乎能够立即清理垃圾然后把这部分内存归还给操作系统。让我们配置如下的JVM参数:

  java -XX:+UseShenandoahGC -Xmx2g -Xms32m 
    -XX:+UnlockExperimentalVMOptions
    -XX:ShenandoahUncommitDelay=1000 
    -XX:ShenandoahGuaranteedGCInterval=10000 -jar app.jar 10

对应的内存趋势图如下所示:

dagcdeneicunshensuonengli_8.png

ShenandoahGC memory

由图可知,ShenandoahGC非常弹性(elastic),即使没有发生任何FGC,它也能马上把不再使用的内存归还给操作系统。

总结

根据上面的测试结果可知,只有ParallelGC不具备内存伸缩能力。而其他的GC,例如:Serial,CMS,G1,ShenandoahGC都具备内存伸缩能力。其中Serial和CMS的效果一般,G1需要借助FGC才能将不再使用的内存归还给操作系统。至于JDK12带来的ShenandoahGC,效果非常好,而且不需要依赖FGC,异步就能完成完成内存伸缩。

Java不断完善和适应不断变化的需求。 因此,其对内存的贪婪对微服务和云托管程序来说不再是问题,因为已经有正确的工具和方法来正确地扩展它,清理垃圾并释放进程的资源。 通过合理的配置,Java对于所有项目都具有成本效益 – 从云原生到传统企业应用程序。

作者:阿飞的博客

来源:https://www.jianshu.com/p/c19c7f5a439c


看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「方志朋」,公众号后台回复「666」 免费领取我精心整理的进阶资源教程
  4. JS中文网,Javascriptc中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,是给开发者用的 Hacker News,技术文章由为你筛选出最优质的干货,其中包括:Android、iOS、前端、后端等方面的内容。目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。

    本文著作权归作者所有,如若转载,请注明出处

    转载请注明:文章转载自「 Java极客技术学习 」https://www.javajike.com

    标题:全面对比5大GC的内存伸缩能力

    链接:https://www.javajike.com/article/1774.html

« Don’t Cross 32 GB!
剖析G1(Garbage First)»

相关推荐

QR code