本文根据线上jvm
环境(Java 8
)以及gc.log
主要梳理ParNew GC
和CMS GC
执行过程
JVM
参数
主要贴出相关一些参数:
|
|
ParNew and CMS
|
|
ParNew
收集器是Serial
收集器的多线程版本,作用于年轻代,采用多线程进行收集,但一样要STW;
345CMS
的全称是Concurrent Mark and Sweep
,作用于老年代,目标是最短停顿时间,我司绝大部分web
服务采用ParNew + CMS
(还有一些G1
)
ParNew
:采用 标记-复制 算法;
CMS
:采用标记-清除 算法;
GC`日志
截取线上一段gc.log
|
|
ParNew GC
|
|
GC
:区别MinorGC
和FullGC
的标识,这里代表的是MinorGC
;Allocation Failure
:MinorGC
的原因,在这里由于年轻代不满足申请的空间,因此触发了MinorGC
(年轻代GC
);ParNew
:收集器的类型,它预示了年轻代使用一个并行的mark-copy
垃圾收集器;873168K->36622K
:收集前后年轻代的使用情况;943744K
: 整个年轻代的容量;3074543K->2238776K
:收集前后整个堆的使用情况;4089472K
:整个堆的容量;0.0316707 secs
:ParNew
收集器标记和复制年轻代活着的对象所花费的时间(包括和老年代通信的开销、对象晋升到老年代时间、垃圾收集周期结束最后的清理对象等的花销);[Times: user=0.14 sys=0.00, real=0.04 secs]
:GC
事件在不同维度的耗时:user
:GC
线程在垃圾收集期间所使用的CPU
总时间;sys
:系统调用或者等待系统事件花费的时间;real
:应用被暂停的时钟时间,由于GC
线程是多线程的,导致了real
小于user+real
,如果是GC
线程是单线程的话,real
是接近于user+real
时间。
CMS GC
CMS GC
是基于标记-清除算法实现的,整个过程分几步:
- 初始标记(
initial-mark
):从GC Root
开始,仅扫描与根节点直接关联的对象并标记,这个过程需要STW
,但是GCRoot
数量有限,因此时间较短 - 并发标记(
concurrent-marking
):这个阶段在初始标记的基础上继续向下进行遍历标记。这个阶段与用户线程并发执行,因此不停顿 - 并发预清理(
concurrent-precleaning
):该阶段并发执行,应用不停顿;在并发标记阶段执行期间,会出现一些刚刚晋升老年代的对象或新分配的对象,该阶段通过重新扫描减少下一阶段的工作;precleaning
是为了减少下一阶段“重新标记”的工作量,因为remark
阶段会STW
- 可终止的预清理(
Concurrent Abortable Preclean
):可中止预清理阶段,运行在并发预清理和重新标记之间,直到获得所期望的eden
空间占用率。不会暂停应用,继续预清理,直到eden
区占用量达到CMSScheduleRemarkEdenPenetration
(默认50%)或达到5秒钟 - 重新标记(
remark
):修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,以保证执行清理之前对象引用关系是正确的。这一阶段需要STW
,时间也比较短暂 - 并发清理(
concurrent-sweeping
):清理垃圾对象,这个过程与用户线程并发执行,不停顿 - 并发重置(
reset
):重置CMS
收集器的数据结构,做好下一次执行GC
任务的准备工作
整个过程中需要STW
的阶段仅有初始标记和重新标记阶段,所以可以说它的停顿时间比较短
参考
https://plumbr.io/handbook/garbage-collection-algorithms-implementations/concurrent-mark-and-sweep