【JVM】四、垃圾收集器的选择与调优

1 确定当前的垃圾选择器

执行: java -XX:+PrintCommandLineFlags -version

-XX:InitialHeapSize=1052925824 -XX:MaxHeapSize=16846813184 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
-XX:+UseParallelGC

发现:-XX:+UseParallelGC,这代表什么含义呢?参考下方:

  • -XX:+UseSerialGC:Serial收集器串行回收+Serial Old收集器串行回收
  • -XX:+UseParNewGC:ParNew收集器并行回收+Serial Old收集器串行回收
  • -XX:+UseParallelGC:Parallel收集器并行回收+Serial Old收集器串行回收
  • -XX:+UseParallelOldGC:Parallel收集器并行回收+Parallel Old收集器并行回收
  • -XX:+UseConcMarkSweepGC:Serial收集器串行回收+CMS收集器并发回收(备用Serial Old收集器)
  • -XX:+UseConcMarkSweepGC -XX:-UseParNewGC:ParNew收集器并行回收+CMS收集器并发回收(备用Serial Old收集器)
  • -XX:+UseG1GC:G1收集器并发、并行执行内存回收

当前垃圾收集器为:

年轻代:Parallel收集器-复制算法、多线程、可控吞吐量

老年代:Serial Old 收集器-标记整理、单线程、GC期间StopTheWorld

PS:java -XX:+PrintFlagsFinal -version | grep : 也可以查询JVM相关参数

2 调优思路&参数&方案

调优目的:低停顿;少FGC;高吞吐;

调优思路

  • 新生代大小适中,偏大会导致老年代过小,偏小会导致MinorGC过多,对象年龄涨的快,且没有足够survivor空间,这样对象还是容易跑到老年代,尽可能的让周期性对象在新生代收掉,比如某些对象只存活几分钟如Session,这些对象没必要留到老年代。
  • survivor空间同理不能过大过小,过大会浪费内存空间,同时减小了eden和old空间,过小会导致对象被迫提前进入老年代
  • 新生代存活周期MaxTenuringThreshold默认15,如果觉得15太长,可以缩短,让注定老年对象提前进入老年代

可调整参数

  • SurvivorRatio:新生代中Eden 区域与Survivor 区域的容量比值, 默认为8, 代表 Eden :Survivor=8∶1
  • PretenureSizeThreshold:直接晋升到老年代的对象大小,设置这个参数后,大于这个参数的对象将直接在老年代分配
  • MaxTenuringThreshold:晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC 之后,年龄就加1,当超过这个参数值时就进入老年代,最大15(对象头只分配了4位最大1111)
  • UseAdaptiveSizePolicy:动态调整Java 堆中各个区域的大小以及进入老年代的年龄
  • HandlePromotionFailure:是否允许分配担保失败,即老年代的剩余空间不足以应付新生代的整个Eden 和Survivor 区的所有对象都存活的极端情况
  • ParallelGCThreads:设置并行GC 时进行内存回收的线程数
  • GCTimeRatio:GC 时间占总时间的比率,默认值为99,即允许1% 的GC 时间。仅在使用Parallel Scavenge 收集器时生效
  • MaxGCPauseMillis:设置GC 的最大停顿时间。仅在使用Parallel Scavenge 收集器时生效
  • CMSInitiatingOccupancyFraction:设置CMS 收集器在老年代空间被使用多少后触发垃圾收集。默认值为68%
  • UseCMSCompactAtFullCollection:设置CMS 收集器在完成垃圾收集后是否要进行一次内存碎片整理,默认true
  • CMSFullGCsBeforeCompaction:设置CMS 收集器在进行若干次垃圾收集后再启动一次内存碎片整理,默认0
  • TargetSurvivorRatio:默认50,即survivor区对象目标使用率为50%。

调优方案:

  • 能YGC解决的,不要留到FGC阶段,年轻代内存一般比老年代内存小,且对象存活率低,复制算法较快
  • -Xms和-Xmx设置同样大小值,避免内存调整
  • -Xmn和SurvivorRatio,调整Eden和Survivor的大小,合适即可。通过工具多分析对象存活率和存活大小。
  • GC收集器调整,现在多核CPU时代,尽量使用并行垃圾收集器。jre8 server模式默认Paralled Scavenge+ParOld,可以调整为ParNew+CMS,降低停顿时间,如果效果还不尽人意,可以试试G1.

3 具体案例

3.1 案例一(懵懂的初次尝试)

某应用程序最初给了2G内存,发现CPU占用特别高,响应很慢,发现大量CPU被13条GC task thread#1 (ParallelGC)线程占用,第一时间先把内存加到16G,然后进行GC分析。

jstat -gc 9801 5000 1000:

 S0C      S1C	    S0U	      S1U   EC        EU        OC         OU         MC      MU      CCSC   CCSU    YGC   YGCT      FGC   FGCT    GCT   
188416.0 188928.0  0.0   182850.1 5214208.0 3947333.3 11185152.0  460441.4  51072.0 50026.8 5760.0 5466.6     85    4.250   2      0.105    4.354
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
S0 +181967.2	S1 -182850.1	eden -4996146	old +5928	ygc +0.059	gc +0.06	约20秒一次GC,每5秒新增对象约1.25G 
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
188416.0 188416.0 181967.2  0.0   5215232.0 218062.5 11185152.0  466369.4  51072.0 50026.8 5760.0 5466.6     86    4.309   2      0.105    4.414

最后发现:运行大半天有93次FGC,具体多久忘记了

  • 调整一:扩大新生代大小为10G,survivor为1G,老年代6G,希望这样让对象尽可能在新生代销毁

结果:90秒一次FGC,每次3秒多,一天近1000次FGC,严重影响程序运行。优化变恶化!

缘由:发现每次老年代清理都会剩余5G+的内存,老年代一共6G,每次仅留1G不到的内存空间留给后续晋升的对象。

发现:发现Eden和Survivor空间大小是动态调整的,虽然设置了1GSurvivor,其实是由几百兆慢慢上下调整的,才想起当前用的是Parallel Scavenge,它会自己调整大小。

  • 调整二:讲年轻代调整为8G,Survivor依旧1G

jstat -gc 1040  500 36000:

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT  
455168.0 388608.0  0.0   388166.9 7502336.0 1836683.8 8388608.0  7810455.3  55552.0 54355.9 6144.0 5889.3   4145  488.767  90    321.415  810.182
...... 
333312.0 331776.0  0.0   298904.3 7719424.0 1994820.6 8388608.0  6904759.1  55552.0 54387.8 6144.0 5886.8   5565  682.398  128   452.292 1134.691

结果:470s一次FGC,12s一次YGC,这样一天大约会发生180次FGC,还是不行,5小时总GC时间1134秒

  • 调整三:改用ParNew+CMS

jstat -gc 20090  500 36000:

 S0C    S1C	     S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1048576.0 1048576.0  0.0    0.0   6291456.0 2203164.6 8388608.0   13928.5   29928.0 29486.9 3656.0 3518.2      0    0.000   4      0.538    0.538
......
1048576.0 1048576.0  0.0   470621.9 6291456.0 5604772.2 8388608.0  7450988.7  51784.0 50569.2 5704.0 5461.4   1538  709.527  28     93.194  802.721

结果: 750s一次FGC,11秒一次YGC,一天大约110次FGC,感觉还行,5小时总GC时间802秒

  • 调整四:改用G1

jstat -gc 20406  500 36000:

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
 0.0   49152.0  0.0   49152.0 8765440.0 4620288.0 7962624.0     0.0     36300.0 35607.3 4300.0 4084.3      2    0.062   0      0.000    0.062
 0.0   376832.0  0.0   376832.0 8437760.0 6119424.0 7962624.0  6904291.3  51532.0 50198.1 5708.0 5449.1    953  240.178   2     29.480  269.658

结果:10298s/171min后才出现第一次FGC,惊喜不,可惜看看FGC时间24秒,STW时间:23秒!吓死......过了450秒,第二次FGC,耗时5.5秒,第二次FGC之后过了2个多小时都没出现第三次FGC。这么看G1的行为不可琢磨。5小时总GC时间269秒。

发现:Survivor空间只有一个,因为是分区间拷贝,不需要两个Survivor。而且Eden和Survivor会不停的调整容量大小。Survivor用了多少就是它的容量,两个值相等。

  • 调整五:-XX:MaxGCPauseMillis=200,设置期望达到的最大GC停顿时间200ms(JVM会尽力实现,但不保证达到)

结果:20小时,5次FGC,平均每次15秒,频率很低,但是确实GC时间太长,没有达到预期(原理所示)的好。

 

总结:调整期间要注意几个数据:1、年轻代新增对象速率2、老年代常驻对象大小3、新生代对象消亡速率;然后按需调整各个年代内存大小。现象上看,总GC时间:Parallel Scavenge+ParOld > CMS+ParNew > G1。所以吞吐量正好相反。但是G1的FGC时间不稳定,可能出现单次较长时间的GC。

 


爱家人,爱生活,爱设计,爱编程,拥抱精彩人生!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qqchaozai

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值