JVM调优实战

调优原因:生产服务器4核16g,框架SpringBoot,当压测1小时左右时,出现很多次Full GC,GCT总时间也很大。 jvm启动参数:

-Xms10G -Xmx10G -Xmn3g

jdk1.8默认GC

问题分析:

老年代7G,会发生多次FullGC,说明老年代的内存慢慢上涨,但是不是内存泄漏,因为如果是内存泄漏,FullGC完应该会OOM。所以就分析老年代内存逐渐跑满的原因:

      一个对象从Eden区创建,到Survivor区域,逃过15次GC,最终才能到老年代,这种老年代的对象,在FullGC发生时,也基本不会被回收。但是压测过程中出现的现象时老年代满了之后,FullGC会回收很大比例的垃圾对象,说明很多对象没逃过15次GC,直接跑到老年代了,所以接下来分析:什么情况对象会 “直接” 跑到老年代?

1、JVM设置了参数(-XX:PretenureSizeThreshold=1000000),大对象直接进入老年代。

2、Minor GC时,Survivor区域太小。

3、老年代内存本身比较小,Minor GC时,有概率触发Full GC

在笔者这里,情况1、3可以排除。只剩下2。

猜测原因:

UseAdaptiveSizePolicy参数导致Survivor动态调整到过小,对象直接到老年代。

jdk8中默认的垃圾收集器,开启了UseAdaptiveSizePolicy,  会动态调整Survivor的大小,当Survivor区域小到不够存放Eden区域一次Minor GC后活下的对象时,对象就会直接进入老年代,压测时间久了,自然会发生很多次full GC,当老年代内存比较大时,FullGC卡顿时间也越久。所以直接关闭UseAdaptiveSizePolicy, 并且通过-XX:SurvivorRatio=N把Survivor区域调整到一定的大小(500M-1G, 越大的话浪费的内存也相对多一些,不过都10G了,也不在乎那一点内存了),Eden区域比例不可过大,否则minor GC耗时也会上去,也不可过小,太频繁的话,GC过程本身也有一定的耗时,积少成多,吞吐也自然会下降。Eden具体大小设置可根据jstat -gc $pid, 分析Eden大小和GC耗时做个对比。

为了验证这个猜测, 笔者本地环境重新压测对比了两轮(持续一小时20分),4核8G的内存,JVM启动参数和GCT耗时分别为:

关闭:UseAdaptiveSizePolicy

local-1、-XX:-UseAdaptiveSizePolicy -XX:SurvivorRatio=3 -Xms4G -Xmx4G -Xmn2G

     GCT: 44秒, FullGC:0次, minor GC:2600次

YGC AVG: 0.017秒/次

开启:UseAdaptiveSizePolicy

local-2、 -Xms4G -Xmx4G -Xmn2G

     GCT: 28秒,FullGC:4次,minor GC:1403次

YGC AVG: 401/14647 = 0.02秒/次

FGC AVG:    207/152   =  0.2秒/次

两次都压测了1个小时左右,结果却证明UseAdaptiveSizePolicy 效果更佳,原因其实不难看出:

        当Survivor区域大小固定后,Eden区域可使用的内存也是固定的,而UseAdaptiveSizePolicy 可以动态缩小Survivor大小,虽然会有概率导致部分对象直接到老年代,但是变向使得Eden的可使用内存变大了,具体来说两点:

1、动态调整Survivor区域,可以让新生代内存中,Eden区域的比例尽可能大,减少minorGC次数

2、把一定比例的对象直接挪到老年代,等于Eden区可以动态的挪用部分老年代的内存作为 “新生代Survivor内存不足时的备用内存”。

明白以上两点时,可以看出UseAdaptiveSizePolicy 的最大问题是:被挪用的老年代内存太大。导致FullGC太久。

我们都知道新生代的GC要比老年代GC快很多,如果被挪用的老年代内存能尽可能小,那么同时新生代比例同时也会变大,那么会带来两个好处:

   1、老年代GC时间缩短

   2、新生代内存比例同时变大了,minor GC 次数少了。(新生代GC很快,内存加大一点,影响不明显)

结论:

所以最优的策略是:开启UseAdaptiveSizePolicy,同时压测时观察GC log,查看老年代Full GC后内存使用率判断老年代内存尽可能可以小到多少。 笔者这里观察老年代实际使用的内存不会超过300M(老年代实际需要存放的往往是经历15轮GC后仍存活的对象

所以又来了一轮压测(持续1小时20分):

开启:UseAdaptiveSizePolicy

local-3、 -Xms4G -Xmx4G -Xmn3600M

     GCT: 10.8秒

FGC AVG:     0.1秒/次

YGC AVG: 0.016 秒/次

实践证明:开启UseAdaptiveSizePolicy 时,老年代内存够用就行!!!

有了这个结论后,再看看生产环境的压测数据:

YGCYGCTFGCFGCTGCT
14647次401秒152次207秒608秒

YGC AVG: 401/14647 = 0.02秒/次

FGC AVG:    207/152   =  1.36秒/次

很明显,问题就是老年代内存太多了!!!!!

jdk8下使用默认GC时,调优宗旨:

 -  当可以预判老年代内存用量时:

      老年代内存够用就行(尽可能小,但是又不会OOM)

-   当无法预判老年代内存用量时:

     尽量从GC Log分析预判老年代内存实际用量。

 备注:UseAdaptiveSizePolicy, 只会动态调整Eden、Survivor From、Survivor To的大小,而新生代和老年代的比例在JVM进程启动的时候就固定了,即在jvm进程销毁前,老年代的大小都不会再变化了。 (未手动指定比例如:-Xmn3g时,则由系统自动决定分配比例。系统决定的比例往往不是最优的!!!)

另外的调优方案:

- 笔者这边生产环境jvm堆内存总量已经10g了,所以笔者尝试换了jkd9的默认GC:G1垃圾收集器,G1不需要配置太多参数,设置堆内存即可,jdk8下开启如:

-Xms10G -Xmx10G -XX:+UseG1GC

G1不需要设置新生代/老年代比例、SurvivorRatio等,因为这些都会由G1自动的动态调整,对比发现G1在jvm大内存下表现更优,几乎不需要什么特殊的调优。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值