在这篇文章 深入浅出JVM(十六)之三色标记法与并发可达性分析 中,我们曾说明过GC线程和用户线程并发执行导致的对象消失问题,可以使用增量更新或原始快照的方式来解决
上文深入浅出JVM(十七)之并发垃圾收集器CMS中描述过使用增量更新的CMS,本文将介绍使用原始快照的G1垃圾收集器
Garbage First
G1 全称 Garbage First 面向服务端的垃圾收集器,G1诞生是为了在延迟可控的情况下尽可能高的吞吐量
Region
G1 不再使用分代收集算法,把Java堆内存分为多个大小相等的区域(Region),面向堆内存任何部分来组成区(Region)进行收集,哪块内存垃圾最多,收益最大就回收哪块
Region分为新生代Eden、新生代Survive、大对象Humongous
大多数情况下Humongous被当成老年代
当大对象太大,一个Humongous不够时,使用连续多个Humongous存储
每个区域设计2个TAMS(Top at Mark Start)指针,把区域中一部分空间划分出来用来为新对象分配内存(指针碰撞,内存规整)
G1从整体上可以看成使用标记-整理算法,从局部上可以看成使用区域间复制算法
执行过程与流程图
G1采用原始快照来解决GC线程与用户线程并发时的对象消失问题
G1收集器在后台维护一个优先级队列跟踪各个区域中的垃圾回收价值(回收垃圾的大小和回收时间的情况)
根据-XX:MaxGCPauseMillis
用户规定的收集停顿时间来优先回收垃圾回收价值最大的区域
执行过程
- 初始标记 : 标记GC Roots能直接关联的对象,修改TAMS指针(停顿用户线程,耗时短)
- 并发标记 : 从GC Roots直接关联对象开始遍历整个引用链的过程(GC线程与用户线程并发执行耗时长),扫描完还要处理原始快照记录在并发时有引用变动的对象
- 最终标记 : 处理并发阶段遗留下来的少量原始快照记录(停顿用户线程,耗时短)
- 筛选回收 : 更新区域的垃圾回收价值,把各个区域的垃圾回收价值(要算成本:回收时间)在优先级队列中排序,根据用户所期望的停顿时间制定回收计划,把要回收的那片区域存活的对象复制到空区域中(复制对象要暂停用户线程,GC线程并行执行)
如果期望停顿时间设置太短(不符合实际),由于停顿时间短,回收垃圾速度<为新对象分配内存速度,会导致堆满Full GC反而会降低性能
参数
-XX:+UseG1GC
使用G1收集器
-XX:G1HeapRegionSize
设置每个region大小
-XX:MaxGCPauseMillis
设置预期停顿时间 (默认200ms,最好不要太小)
-XX:ParallelGCThread
设置GC线程数
-XX:ConcGCThreads
设置并发标记线程数
-XX:InitiatingHeapOccupancyPercent
设置触发老年代GC的堆占用率阈值
特点
优点
- 整体:标记-整理、局部:复制,不会产生内存碎片
- 从用户期望时间来回收垃圾价值最高的垃圾
- 原始快照的好处(记录旧引用,不需要添加新引用记录,减少并发标记、重新标记阶段的消耗)
缺点
- 因为有很多区域,跨区域引用对象问题频繁出现,每个区域要维护记忆集(Key:别的区域地址 Value:集合存储卡表的索引号),卡表实现复杂(其他卡表:我指向谁,这里的卡表:我指向谁+谁指向我),所以占用内存更大
- 执行负载大(写屏障操作复杂)
写后屏障维护复杂的卡表
原始快照的坏处(需要记录旧引用):写前屏障跟踪并发指针变化,在用户程序运行中产生有跟踪引用变化带来的额外负担
CMS写屏障可以直接同步,而G1写屏障太复杂要把写前屏障和写后屏障中做的事放到队列中,异步处理
总结
本篇文章深入浅出的介绍G1并发垃圾收集器Region、执行过程、流程图、参数、特点等知识
G1不使用分代算法,将内存分为Region,Region分为Eden、Survive、Humongous,从局部可以看成复制算法,整理上看成标记-整理算法
G1初始标记枚举GC根节点,在并发标记中使用原始快照解决对象消失问题,最终标记后使用优先级队列优先回收价值高的region
G1不会产生内存碎片,能够在期望的低延迟中完成价值最大的清理,但维护跨代引用、写后屏障等开销大
内存较大、处理器较多、期望低延迟、面向服务端的垃圾收集器可以选择G1
最后(一键三连求求拉~)
本篇文章将被收入JVM专栏,觉得不错感兴趣的同学可以收藏专栏哟~
本篇文章笔记以及案例被收入 gitee-StudyJava、 github-StudyJava 感兴趣的同学可以stat下持续关注喔~
有什么问题可以在评论区交流,如果觉得菜菜写的不错,可以点赞、关注、收藏支持一下~
关注菜菜,分享更多干货,公众号:菜菜的后端私房菜