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垃圾回收机制引用计数标记清除标记整理)