25、JVM 调优实战 - 线上系统部署采用G1垃圾回收器,如何设置参数?

1. 什么时候出发新生代 + 老年代的混合垃圾回收?

G1有个参数,是 “-XX:InitiatingHeapOccupancyPercent” ,默认值是45%。意思是老年代占据了堆内存的45%的 Region时,就会触发一个新生代 + 老年代一起回收的混合回收阶段。

2. G1垃圾回收的过程

首先会触发一个 “初始标记” 操作,这一过程需要进入 STW,但仅仅是标记一下 GC Roots 直接能引用的对象,速度是很快的。

整个过程是,先停止系统程序的运行,然后对各个线程栈内存中的局部变量代表的 GC Roots,以及方法区中的类静态变量代表的 GC Roots,进行扫描,标记出来他们直接引用的那些对象。

 

接着会进入 “并发标记” 的阶段,这个阶段会允许系统程序的运行,同时进行 GC Roots追踪,从 GC Roots 开始追踪所有的存活对象。

 

JVM会对并发标记阶段对象作出的一些修改记录起来,比如说哪个对象被新建了,哪个对象失去了引用。

接着是下一个节点,最终标记阶段,这个阶段会进入 “STW” ,系统程序是禁止运行的,但是会根据并发标记阶段记录的那些对象修改,最终标记一下有哪些存活对象,有哪些是垃圾对象,如下图:

 

最后一个阶段,就是 “混合回收” 阶段,这个阶段会计算老年代中每个 Region 中的存活对象数量,存活对象的占比,还有执行垃圾回收的预期性能和效率。

接着会停止系统程序,然后全力以赴尽快进行垃圾回收,此时会选择部分 Region 进行回收,因为必须让垃圾回收的停顿时间控制在我们指定的范围内。

 

而且,由于老年代对堆内存占比达到 45% 的时候,触发的是 “混合回收”。也就是说,此时垃圾回收不仅仅是回收老年代,还会回收新生代,还会回收大对象。

根据情况,因为设定了对 GC 停顿时间的目标,所以说他会从新生代、老年代、大对象里各自挑选一些Region,保证用指定的时间(比如200ms) 回收尽可能多的垃圾,这就是所谓的混合回收

 

3. G1垃圾回收器的一些参数

G1回收器进行垃圾回收的最后一个阶段混合回收的时候,会执行 STW,所以说G1是允许执行多次混合回收的。

比如先停止工作,执行一次混合回收掉一些 Region,接着恢复系统运行,然后再次停止系统运行,再执行一次混合回收掉一些 Region。

有个参数,“-XX:G1MixedGCCountTarget” 参数,默认值为8。表示在一次混合回收的过程中,最后一个阶段执行几次混合回收。即可以不间断地反复执行8次混合回收。

为什么要反复回收多次呢

因为停止系统一会儿,回收掉一些 Region,再让系统运行一会儿,然后再次停止系统一会儿,再次回收掉一些 Region,这样可以尽可能让系统不要停顿时间过长,可以在多次回收的间隙,也运行一下。

还有另一个参数,“-XX:G1HeapWastePercent” ,默认值为 5%。意思是在混合回收的时候,对 Region 回收都是基于复制算法进行的,都是要回收的 Region 里的存活对象放入其他 Region,然后这个 Region 中的垃圾对象全部清理掉。

 

这样在回收过程就会不断空出来新的 Region,一旦空闲处理的 Region 数量达到了堆内存的 5%,此时就会立即停止本次混合回收。

G1整体是基于复制算法进行 Region 垃圾回收的,不会出现内存碎片的问题,不需要像 CMS 那样标记 - 清理之后,再进行内存碎片的整理。

还有一个参数,“-XX:G1MixedGCLiveThresholdPercent” ,它的默认值是 85%,意思是确定要回收的 Region 的时候,必须是存活对象低于 85%的 Region 才可以进行回收。否则,回收成本就很高。

4. 回收失败时的Full GC

如果在进行Mixed回收的时候,无论是年轻代还是老年代都基于复制算法进行回收,都要把各个Region的存活对象拷贝到别的 Region里去。

如果拷贝的过程中发现没有空闲 Region 可以承载自己的存活对象,就会触发一次失败。

一旦失败,就会立刻切换为停止系统程序,然后采用单线程进行标记、清理和压缩整理,空闲出来一批 Region,这个过程是极慢极慢的。