lua gc算法 - 标记清除

Posted Lua探索之旅

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了lua gc算法 - 标记清除相关的知识,希望对你有一定的参考价值。

垃圾回收


垃圾回收(garbage collect,简称gc)是虚拟机区别于传统c/c++开发的重要标志,程序员终于告别头疼的内存越界、泄漏等问题,在性能要求不是非常高的场合,可以放心使用vm的垃圾回收功能。


lua虚拟机在5.0版本后使用的是增量式垃圾收集算法,该算法是标记清除(mark sweep)算法的升级版本,也称为三色标记清除算法。本文将介绍标记清除这一简单的gc算法,该算法在lua5.0版本之前使用。


以一段代码段为例:

for i=1,100 do
 a = { }
end
print(a)

该代码段循环100次,每次循环创建一个table,赋值给a。


可以看到,前99次创建的 table由于没有任何变量引用,变成了垃圾资源,那么虚拟机该如何回收这些垃圾table呢?


一个运行中的vm主要包括运行栈、全局对象表、指令列表,其中指令列表作为输入数据,那么任意一个活跃的变量要么在运行栈里,要么在全局表里:

活跃变量 in { 运行栈 or 全局对象表 }


如果一个内存对象没有被任何活跃变量引用,那么该内存对象就是垃圾资源,需要vm去回收。根据以上原理,可以得到一个结论:

一个内存对象要么被运行栈引用,要么被全局对象引用,
否则ta就是垃圾资源。




标记清除算法


(1)管理gc对象

回到上面的代码段,table是vm运行时动态创建的内存对象,需要由vm自动回收,那么vm如何知道有哪些table需要回收呢?


方法是将每次创建的table用链表串联起来,比如for循环已经创建了3个table,那么链表就是:

gclist -> table3 -> table2 -> table1


每次将新对象插入链表的头部位置,并且记录对象的gcstate为未标记(no mark)


(2)执行gc

当达到gc阈值后,比如gclist长度达到20个后,执行一次gc。 


根据上面的分析,有效的内存对象要么被运行栈引用,要么被全局对象引用,可以先扫描一遍运行栈和全局对象表:

for obj in 运行栈
 标记 obj
end
for obj in 全局对象表
 标记 obj
end

将有效的内存对象包括其内部的子对象的gcstate为标记(marked)


(3)清除垃圾

完成标记后,遍历一次gclist,将仍未标记的对象从链表中取出并释放,剩余的都是有效对象,将剩余对象的gcstate再重置为未标记


以上就是标记清除算法的实现逻辑,简单描述就是标记有用的资源,清除未标记的垃圾资源,流程图如下所示:


算法思考


(1)阈值如何设置?

gc是为了及时释放垃圾资源,避免内存用尽,但gc本身会暂停正常逻辑,占用一定的cpu时间,严重时会影响程序性能。


假若机器内存非常大,比如有100G,vm每小时会产生1G的垃圾资源,那么可以每隔10小时检查一次gc,比如在半夜3~4点专注释放垃圾2小时,也没什么问题。 或者启动一个新服务,直接kill掉当前服务,也没啥问题。


假若机器内存有限,就必须考虑尽快释放垃圾资源,但是也不能每次创建对象时都执行一次gc,必须设定一个阈值,超过阈值后再执行gc。


简单的方法就是设定一个基础值,比如100,即gclist串联的对象少于100时,不执行gc。


超过100时,执行一次gc,完成gc后,用max(gclist剩余长度 * 2,100)作为新阈值,乘以2的是为了当内存对象持续增加时,动态提升阈值。


(2)gc性能

标记清除算法是典型的stop  the world实现,对于响应速度要求高的服务来说,gc可能是个瓶颈,所以需要将gc步骤拆分为多步,比如一次gc需要10ms,可拆分为多次执行,每次只需要1~2ms,但多步gc的实现更加复杂,增量式垃圾回收就是通过增加颜色标记实现的。

以上是关于lua gc算法 - 标记清除的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript性能优化1——内存管理(JS垃圾回收机制引用计数标记清除标记整理)

GC原理---垃圾收集算法

JVM中的GC垃圾回收

26 Java GC算法 垃圾收集器标记 -清除算法复制算法标记-压缩算法分代收集算法

Atitit.常用的gc算法

JVM学习笔记GC——JAVA语言的垃圾回收