Lua5.0及其更早的版本采用的是二色标记法:
1.从root对象组(所有最上层定义的对象)开始遍历,所有可达对象都被标记为黑色。
2.等遍历完成后,剩余的白色对象即为可回收对象,回收这些白色对象。
3.重置黑色对象为白色对象,等待下次回收过程。
此算法有明显的缺点,就是每次执行都会把整个虚拟机停下来(Stop the World),所以会有明显的卡顿,为此它采取的策略是:每当内存分配总量超过上次GC后的2倍,就跑一次GC流程。即使如此,再实践中的卡顿问题也很明显,因此5.0之前的lua多用于内嵌脚本,承担小部分工作。
Lua5.1采用三色标记法清理:
白色:在gc开始阶段,所有对象颜色都为白色,如果遍历了一遍之后,对象还是白色的将被清除。
灰色:灰色用在分步遍历阶段,如果一直有对象为灰色,则遍历将不会停止。
黑色:确实被引用的对象,将不会被清除,gc完成之后会重置为白色。
总结一下:
1.从root开始遍历节点,白色节点设为灰色,加入到处理灰色的链表中gray。
2.在gray中继续遍历,发现其下方有子节点,把子节点加入到gray中,并且自己置为黑色。
3.重复1,2步骤直到都遍历完。
4.清理剩余的白色节点,如果产生了节点重新关联则标记为另一种白色节点,此次先跳过
Lua 5.1 GC解决了Stop the World的问题,但是总工作量没有变,依然要遍历全部对象。
为此,在Lua5.2尝试性的引入了分代GC(generational gc),
分代GC的产生是基于这样一个假设:大部分对象被分配出来后很快就回收掉了,
在这种情况下,垃圾收集器可以集中精力对付刚刚构造出来的年轻对象,每次收集只处理年轻对象,之后内存增长到一定程度时再全部处理一次。这个思路没错,但是5.2版本提供的分代GC过于简单,任何活过当前收集周期的对象就会变老,实践中会造成很多不该变老的对象迅速变老。因此在Lua5.3中移除了分代GC。
在最新的Lua5.4中,分代GC又被重新设计实现,将原来的一轮变老改成了活过两轮才变老,当前5.4版本中步进式( incremental gc )和分代GC( generational gc )同时存在,并可自由切换。
Lua5.4可采用分代式GC:
这里的分代式算法就是上面的三色标记法。
但区别在于,5.1的是gc过后剩下的节点下次gc时还是白色。
而这里是会加入到老年代列表,老年代列表的节点是不会被gc到。这样的好处是下次采用分代式gc不需要全部遍历所有节点,代价就是可能老年代确实存在着一些不需要使用的可回收对象这时候不会回收。这种设计属于经验设计。
这里实际上是二轮老年链表,这里的gc不会被遍历。但有可能因为其下新增了白色节点而被从老年列表里拉出来参入到下一轮的gc。
原理如下:
简单来说,就是这次的gc如果超过设置的值,下次就触发全量gc,如果不是则为分发式gc。这个值是由上面设置的参数决定的。
另附gc使用方法: