GC 什么时候触发
1.系统自动触发。当堆大小达到阈值,或距离上一次GC时间超出设定值。
2.手动触发,runtime.GC()
V1.3前标记清除(mark and sweep)
步骤:
- 先全局暂停,不执行用户代码 启动STW。stop the world。
- 标记可达对象(程序所用到的对象引用链)
- 清除没有标记的对象
- 停止 STW,继续执行用户代码
缺点三个:
SWT 的暂停,会造成卡顿。需要扫描整个 heap 区。会产生 heap 碎片。
V1.3标记清除
1.3前,STW 的时间段覆盖了整个1~4步骤。
1.3优化点,将3和4调换位置。最终达到 STW 的时间段仅覆盖1~3。
V1.5三色并发标记
三色:白(垃圾),灰(待遍历),黑(可达对象)。
程序是这些对象的根节点集合。可以想象成程序启动,各个包(实际上是变量,gc 的是变量)的引用形成的树结构。
步骤:
- 新创建的对象都标记为白色。
- 仅从根节点遍历一层,遍历到的标记灰色。并将对象移入灰色区。
- 遍历灰色。将灰色引用的白色对象标记为灰色,移入灰色区。将之前遍历的灰色标记为黑色。
4.重复第三步。直到灰色区域为空。
5.将白色区域全部删除回收。
以上也需要 STW。如果没有 STW,会在第二步和第三步之间出现问题:
程序进行过程中,对象2解除对对象3的引用,同时,对象4引用了对象3。(新增了,删除了。对应插入屏障,删除屏障)
按道理来说对象 3 就不应该被干掉。
但是走到第三步时,遍历对象2时,发现并没有对对象 3 进行引用。
而下面,将跳过对象 4,直接对对象7进行遍历,也忽略了对象4对对象3的引用。
最终就会导致对象 3,成为了不可达对象,即一直是白色,最终被回收。
无 SWT 的三色标记总结
可以看出,有两种情况,在三色标记法中,是不希望被发生的。
- 条件1: 一个白色对象被黑色对象引用(白色被挂在黑色下)。即新增了引用关系。
- 条件2: 灰色对象与它之间的可达关系的白色对象遭到破坏,(灰色同时丢了该白色)。即删除了引用关系。
如果当以上两个条件同时满足时,就会出现对象丢失现象!
并且,如果示例中的白色对象3还有很多下游对象的话, 也会一并都清理掉。
可是SWT还是很影响效率,由这里引出了屏障机制。其机理就是破坏以上两个条件。
v1.5 三色标记的插入屏障和删除屏障
分别破坏上述条件的两种方式
- 强三色不变式。不允许黑色下挂载白色。破坏条件 1。
- 弱三色不变式。允许黑色下挂载白色,但要求该白色的上游链条中含有灰色。即白色必须还被其他灰色挂载(间接的也行)。破坏条件 2。
为了实现这两种方式,GC 增加了两种屏障机制,插入屏障,删除屏障。
插入屏障(强三色不变式)
1.只对堆区进行插入屏障,即黑色对象新增白色对象,会将白色修改为灰色。之后再重复第三步。从而满足黑色下不再是白色。
2.而栈区,上图可看到黑色对象1,新增了白色对象 9,并没有将对象9改为灰色。
3.栈区进行 SWT。重新再扫描,即重复第三步。会将对象 9 变为灰色,再循环对象 9 会变成黑色。(即栈区会多一次 SWT 和再扫描循环过程)。
删除屏障(弱三色不等式)
1.被解除引用的对象如果为灰色或白色,均标记为灰色。解决了删除后的关系是灰解除灰,而不是灰色到白色。
这种方式精度低。以上图为例子。对象 2 删除对对象 3 的引用后,对象 3 也会变为灰色,重复第三步骤几轮后对象 3 也会变为黑色。
而既然对象 3 已经跟程序没关系了,就应该被清除掉。但是这一次对象 3 活过了这一轮。需要到下次 GC 才能被清除。所以说精度低。
缺点总结
V1.8 混合写屏障
1.GC 开始,将栈上的所有对象标记为黑色。
2.GC 期间,栈上创建的新对象均标记黑色。
3.GC 期间,堆上。被删除的,被添加的,标记为灰色。即,只有堆区做混合写屏障。
无 V1.5 的两个缺点。
总结
共四次演化。
V1.3 前。有 STW。找可达,清未标记。
V1.3,缩短 STW 时间。找可达,清未标记。
V1.5,对栈区 STW。引入三色,黑色可达,灰色待遍历,白色垃圾。引入插入屏障和删除屏障。插入屏障黑色引用白色,会将白色标记为灰色。删除屏障,被删除的都标记为灰色。缺点需要 STW 栈区,删除屏障不精准。
如果V1.5 无 STW,则会出现实际引用对象丢失的情况(黑色 A 引用白色 B,灰色 C 断开了和白色B的引用)。
V1.8,栈区全黑,堆上被删除引用的和被添加的标记为灰色。无 STW。