Skip to main content Link Menu Expand (external link) Document Search Copy Copied

1. 背景

1.1. 现状

  • 限售限量查询QPS 9W
  • jvm machine 8C16G 45台
  • 32台 redis 64G
  • 13 Job read data from redis to local cache,Every 20 minutes on average

1.2. 问题

  • old区持续增长,10小时一次FGC,一小时增长600M ,10小时达到了6G , 一次800ms的STW。
  • YGC一定频率出现耗时长的YGC,引发调用超时
  • redis 毛刺,cpu达到40%

2. 问题分析

  1. job频率过高,导致FGC?

在使用CMS作为老年代的GC器,默认maxTenuringThreshold=6,( G1=16)的前提下, YGC会更快速的晋升到OLD区; 再者,本地缓存使用的强引用,在第二次全量同步前, 就进入了OLD区

定时任务错开执行(让每个定时任务产生的对象进入old区之后再执行下个job) 避免一次young gc复制两次job的对象增加单次young gc的时长

  1. YGC频繁且耗时长

parnew作为年轻代垃圾回收器,复制对象到OLD区。

  1. maxTenuringThreshold调整对性能的影响

越大:YGC耗时越长 越小:FGC增长越快

3. 优化目标

  1. 降低GC次数
    • YGC次数
    • FGC次数
  2. 降低GC时延

4. 使用G1作为垃圾回收

4.1. 堆内存结构:

G1将Java堆划分为多个大小相等的独立区域(Region),JVM最多可以有2048个Region。 一般Region大小等于堆大小除以2048,比如堆大小为4096M,则Region大小为2M, 当然也可以用参数”-XX:G1HeapRegionSize”手动指定Region大小,但是推荐默认的计算方式。 G1保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。 默认年轻代对堆内存的占比是5%,如果堆大小为4096M,那么年轻代占据200MB左右的内存, 对应大概是100个Region,

可以通过“-XX:G1NewSizePercent”设置新生代初始占比,在系统运行中,JVM会不停的给年轻代增加更多的Region, 但是最多新生代的占比不会超过60%,可以通过“-XX:G1MaxNewSizePercent”调整。 年轻代中的Eden和Survivor对应的region也跟之前一样,默认8:1:1,假设年轻代现在有1000个region,eden区对应800个, s0对应100个,s1对应100个。一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代,

也就是说Region的区域功能可能会动态变化。G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样, 唯一不同的是对大对象的处理,G1有专门分配大对象的Region叫Humongous区,而不是让大对象直接进入老年代的Region中。 在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按照上面算的,每个Region是2M,只要一个大对象超过了1M,就会被放入Humongous中, 而且一个大对象如果太大,可能会横跨多个Region来存放。Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开销。 Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。

4.2. G1提供了两种GC模式,Young GC和Mixed GC

两种都是完全Stop The World的

Young GC:选定所有年轻代里的Region。通过控制年轻代的Region个数,即年轻代内存大小,来控制Young GC的时间开销。

Mixed GC: 选定所有年轻代里的Region,外加根据global concurrent marking统计得出收集收益高(垃圾占比高的region)的若干老年代Region。 在用户指定的开销目标范围内尽可能选择收益高的老年代Region, Mixed GC不是Full GC,它只能回收部分老年代的Region,如果Mixed GC实在无法跟上程序分配内存的速度, 导致老年代填满无法继续进行Mixed GC,就会使用serialold GC (Full GC) 来收集整个GC heap 所以本质上,G1是不提供Full GC的

4.3. ParNew CMS 对比

parnew+cms收集器,parnew使用复制算法回收年轻代,cms使用标记清除算法回收old区。 cms回收old区会产生内存碎片,导致内存不连续。

CMS是在STW时期做的碎片整理 G1在回收内存后,马上在碎片整理

G1 mixed gc使用复制算法回收年轻代+老年代,没有内存碎片的问题。

优化前:CMS FULL GC:平均800ms
优化后:G1 Mixed Gc(50ms以内) 拆分CMS FUll GC

4.4. G1参数

InitiatingHeapOccupancyPercent

itiatingHeapOccupancyPercent,他的默认是45%,他的含义就是老年代到了45%的时候,就会进行混合回收,比如有2048个region,其中老年代占据了有1000个region,就会混合回收.

G1HeapWastePercent

在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC

G1OldCSetRegionThresholdPercent

一次Mixed GC中能被选入CSet的最多old generation region数量

G1MixedGCCountTarget

G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数

4.5. G1 常见问题

  • G1什么时候触发mixed gc?

-XX:G1HeapWastePercent: 在globalconcurrent marking(标记old区的垃圾对象)结束之后, 我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前会检查垃圾占比是否达到此参数(默认5%), 只有达到了,下次才会发生Mixed GC(最多接着8次Mixed GC,一次MixedGC 回收的old区不超过总old region的百分之十)。

  • 什么时候触发 globalconcurrent marking?

通过-XX:InitiatingHeapOccupancyPercent指定触发全局并发标记的老年代使用占比,默认值45%,也就是老年代占堆的比例超过45%。

  • G1什么时候触发young gc?

G1YGC在Eden充满时触发,在回收之后所有之前属于Eden的区块全部变成空白,即不属于任何一个分区。

5. 总结

  • G1弱化了分代的概念,重视分区的重要性,
  • 合适的应用场景:大内存,多核心的硬件条件,如ElasticSearch、