在这篇博客《JVM(2)——GC算法和收集器》中,简单介绍了GC的算法和收集器。今天主要介绍其中的一种收集器G1。
一、目标
尽最大可能减少STW时间;
GC停顿可控;
不产生内存碎片和浮动垃圾。
二、特点
1、化整为零,引入region。
Region
Java堆划分成多个独立区域Region,新生代和老年代也不再是物理隔离的,他们都是一部分Region集合。新增的Humongous区,专门用来存放临时的大对象。如下图:
RSet
Remembered Set,每个Region初始化时都会有一个RSet,类似一个Hash Table,记录了哪些Region对当前Region的引用【key:Region的起始地址 value:Card Table的index】。在进行后续标记的过程中,RSet也会做同步修改。
用空间换时间,以避免对全堆扫描。
Card Table
也叫卡表,通常是字节数组,卡表中的每个区域叫卡。由Card的索引数组下标来标识每个分区的空间地址。用来标记是否被引用,0-未被引用 1-被引用。
2、多种收集算法
分代收集,整体上“标记-整理”,局部是“复制”。
设计思路:面向局部收集
分代收集:不需要和其他收集器配合就能独立管理整个GC堆,分代能达到更好的收集效果。
空间整合,避免内存碎片。
G1的垃圾回收模式有两种:Young GC 和Mixed GC(混合回收)。
Young GC
该模式下,主要处理Eden->Survivor,Survivor->Old,Eden->Old等。和之前提到的年轻代GC算法没有太大区别,该阶段主要使用了复制等算法。
Mixed GC:
主要针对年轻代和部分老年代区域进行回收。
步骤 | 作用 | 是否需要STW |
---|---|---|
初始标记 | 标记GC Root直接关联的对象 | 是 |
并发标记 | 从GC Root做可达性分析,重新处理STAB记录下的并发时有引用修改的对象 | 否 |
最终标记 | 处理并发标记结束后仍遗留的SATB记录 | 是 |
筛选回收 | 回收效率计算;并进行GC | 是 |
下图为初始标记示意图:
-
哪些可以作为GC Root直接关联的对象?
静态变量(上图左下角方法区中所示),方法的局部变量(可以理解成上图右侧GC回收线程开始的GC Root)。 -
STW(停顿)如何体现?
上图左侧的系统线程处于“禁止运行”状态。 -
G1的并行与并发如何体现?
并行:充分利用多核CPU资源。
并发:第二个步骤并发标记,在该阶段,垃圾回收线程和用户程序(上图左上角系统线程)并发执行。且由于是并发处理,可能会出现标记产生变动。 -
并发标记阶段,如何区分哪些对象被访问过?
G1采用原始快照SATB(Snapshot At The Beginning)来实现,在GC开始时,对活着的对象进行一个快照。之后用三种颜色标记对象:白色-未被访问过,黑色-已经被访问过且对象的所有引用也被扫描过了,灰色-已经被访问过,但对象至少有一个引用还没被扫描过。(引用的是白色)
快照三色关系图示:
GC前 | GC后 | 是否会被回收 |
---|---|---|
白色 | 白色 | 是-对象不可达 |
白色 | 黑色 | 否-安全 |
黑色 | 黑色 | 否-安全 |
灰色 | 黑色 | 否-安全 |
3、可预测的停顿
G1追求低停顿,并且建立可预测的停顿时间模型。
(1)如何做?
避免在整个堆中进行全区域的GC。G1维护一个Region的优先列表,按回收价值排序。
回收价值=回收所获得的空间大小/回收所需时间。
在GC时,优先回收价值最大的Region。这样在一定程度上保证了回收的效率。
region | 回收空间 | 回收所需时间 | 回收价值 |
---|---|---|---|
RegionA | 200ms | 20MB | 0.1 |
RegionB | 1000ms | 10MB | 0.01 |
… | … | … | … |
(2)G1如何拿到各个Region的回收价值?(包括:预计回收空间、回收所需时间等)
G1内部收集每个Region回收耗时,再根据历史数据的偏差、置信度等统计数据,由哪些Region组成的回收集合才能达到期望停顿值之内的最高收益。