Lua的GC回收机制

这篇博客详细介绍了Lua的垃圾回收机制,包括GC的原理、数据结构、Lua5.1后采用的三色增量标记清除算法以及GC的具体过程,如新建对象阶段、触发条件、标记和清除阶段。博客探讨了不同阶段的源码实现,强调了三色标记如何提高系统实时性,并分析了GC过程中的关键函数和状态机。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GC的原理及其算法设计

不同的语言,对GC算法的设计不同,常见的GC算法是引用计数和Mark-Sweep算法, c#采用的是Mark-sweep && compact算法, Lua采用的是Mark-sweep算法,分开说一下:

引用计数算法:在一个对象被引用的情况下,将其引用计数加1,反之则减1,如果计数值为0,则在GC的时候回收,这个算法有个问题就是循环引用。

Mark-sweep算法:每次GC的时候,对所有对象进行一次扫描,如果该对象不存在引用,则被回收,反之则保存。

在Lua5.0及其更早的版本中,Lua的GC是一次性不可被打断的过程,使用的Mark算法是双色标记算法(Two color mark),这样系统中对象的非黑即白,要么被引用,要么不被引用,这会带来一个问题:在GC的过程中如果新加入对象,这时候新加入的对象无论怎么设置都会带来问题,如果设置为白色,则如果处于回收阶段,则该对象会在没有遍历其关联对象的情况下被回收;如果标记为黑色,那么没有被扫描就被标记为不可回收,是不正确的。

为了降低一次性回收带来的性能问题以及双色算法的问题,在Lua5.1后,Lua都采用分布回收以及三色增量标记清除算法(Tri-color incremental mark and sweep)

标记:每次执行GC时,先以若干根节点开始,逐个把直接或间接和它们相关的节点都做上标记;
清除:当标记完成后,遍历整个对象链表,把被标记为需要删除的节点一一删除即可。

其基本的原理伪代码,参考书中原文为:

每个新创建的对象颜色设置为白色

//初始化阶段

遍历root节点中引用的对象,从白色置为灰色,并且放入到灰色节点列表中

//标记阶段

while(灰色链表中还有未扫描的元素):

从中取出一个对象,将其置为黑色

遍历这个对象关联的其他所有对象:

if 为白色

标记为灰色,加入到灰色链表中(insert to the head)

//回收阶段

遍历所有对象:

if 为白色,

没有被引用的对象,执行回收

else

重新塞入到对象链表中,等待下一轮GC

Lua垃圾回收中的三种颜色
所谓的颜色就是上文中“算法简述”提到过的标记,lua用白、灰、黑三色来标记一个对象的可回收状态。(白色又分为白1、白2)
白色:可回收状态。
详解:如果该对象未被GC标记过则此时白色代表当前对象为待访问状态。举例:新创建的对象的初始状态就应该被设定为白色,因为该对象还没有被GC标记到,所以保持初始状态颜色不变,仍然为白色。如果该对象在GC标记阶段结束后,仍然为白色则此时白色代表当前对象为可回收状态。但其实本质上白色的设定就是为了标识可回收。
灰色:中间状态。
详解:当前对象为待标记状态。举例:当前对象已经被GC访问过,但是该对象引用的其他对象还没有被标记。
黑色:不可回收状态。
详解:当前对象为已标记状态。举例:当前对象已经被GC访问过,并且对象引用的其他对象也被标记了。
备注:白色分为白1和白2。原因:在GC标记阶段结束而清除阶段尚未开始时,如果新建一个对象,由于其未被发现引用关系,原则上应该被标记为白色,于是之后的清除阶段就会按照白色被清除的规则将新建的对象清除。这是不合理的。于是lua用两种白色进行标识,如果发生上述情况,lua依然会将新建对象标识为白色,不过是“当前白”(比如白1)。而lua在清扫阶段只会清扫“旧白”(比如白2),在清扫结束之后,则会更新“当前白”,即将白2作为当前白。下一轮GC将会清扫作为“旧白”的白1标识对象。通过这样的一个技巧解决上述的问题。如下图:(下图中为了方便颜色变换的理解,没有考虑barrier的影响)
在这里插入图片描述

GC的数据结构

分析Lua中对于需要GC的类型数据

#define iscollectable(o) (ttype(o) >= LUA_TSTRING)

都会有一个基本的定义CommonHeader,其定义为:
在这里插入图片描述
next: GCObject链表指针,该指针用来将所有的GC对象都链接在一个表中;

tt: 数据类型:nil, boolean, number, string…

marked: 标记字段,byte表示的字段颜色定义为在这里插入图片描述

这儿特定解释一下为什么会有两种白色,前面提到,5.1后的Lua采用的是三色标记算法,其实质是四色标记算法,分为0型白色和1型白色,在GC回收的时候,会设置当前的白色为其中一种,详见globalstate中的currentwhite,这样在代码回收的时候,如果当前对象的白色不为currentwhite,则认为其不可回收,这样的对象需要等到下一次的GC才能决定是否回收,具体参看后面的,会有对应的应用。对于global_state的设计为:
在这里插入图片描述

Lua垃圾回收详细过程

在这里插入图片描述

步骤源码详解

1.新建对象阶段

STEP1:新建可回收对象,将其颜色标记为白色
解释:首先lua对数据类型进行了抽象,具体数据结构可见如下源码:
lobject.h

** Common type for all collectable objects
*/
typedef struct GCObject GCObject;

/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader  GCObject *next; lu_byte tt; lu_byte marked

/*
** Common type has only the common header
*/
struct GCObject {
   
   
  CommonHeader;
};


/*
** Tagged Values. This is the basic representation of values in Lua,
** an actual value plus a tag with its type.
*/
/*
** Union of all Lua values
*/
typedef union Value {
   
   
  GCObject *gc;    /* collectable objects */
  void *p;         /* light userdata */
  int b;           /* booleans */
  lua_CFunction f; /* light C functions */
  lua_Integer i;   /* integer numbers */
  lua_Number n;    /* float numbers */
} Value;

#define TValuefields  Value value_; int tt_

typedef struct lua_TValue {
   
   
  TValuefields;
} TValue;

由代码分析可知,Lua的实现中,数据类型可以分为需要被GC管理回收的对象、按值存储的对象。而STEP1中提及的“可回收对象”(GCObject)就是指那些需要被GC管理回收的对象,在lua5.3中,具体是指:TString、Udata、Cloure、Table、Proto、lua_State(可通过查看GCUnion)这些数据类型。这些数据类型都有一个共同的部分CommonHeader:
lobject.h

#define CommonHeader
GCObject *next; lu_byte tt; lu_byte marked

其中next链接下一个GCObject,tt表明数据类型,marked用于存储之前提到的颜色。
创建上述数据类型对象是通过lua虚拟机调用对应的数据类型创建函数完成的。在创建过程中,总会调用luaC_newobj这个函数,来完成GCObject的初始化。
以Table为例,会依次调用:
(lvm.c)[luaV_execute]->(ltable.c)[luaH_new]->(lgc.c)[luaC_newobj]
下面我们来看luaC_newobj:
lgc.c

/*
** create a new collectable object (with given type and size) and link
** it to 'allgc' list.
*/
GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) {
   
   
  global_State *g = G
### Lua 中基于 `collectgarbage("step")` 的定时垃圾回收Lua 中,可以通过调用函数 `collectgarbage("step", arg)` 来执行增量式的垃圾回收操作。参数 `arg` 表示每次步进所花费的时间比例,单位为秒的一部分[^1]。具体来说: - 调用 `collectgarbage("step", n)` 会尝试运行一次垃圾回收步骤,其中 `n` 是一个数值,表示本次垃圾回收的持续时间相对于总目标时间的比例。 - 如果返回值为 `true`,则说明此次步进完成了整个垃圾回收周期;如果返回值为 `false`,则意味着还需要更多的步进来完成当前的垃圾回收。 为了实现定时触发垃圾回收的功能,可以结合循环结构以及延迟机制来定期调用 `collectgarbage("step")` 方法。下面是一个简单的例子展示如何设置每秒钟进行一定次数的垃圾回收步进操作: ```lua -- 定义一个函数用于逐步清理垃圾并控制频率 function timedGarbageCollection(interval, stepsPerInterval) local startTime = os.clock() -- 获取起始时间戳 while true do local currentTime = os.clock() -- 计算自上次迭代以来经过了多少秒 if (currentTime - startTime >= interval) then for i = 1, stepsPerInterval do collectgarbage("step", 0.1) -- 进行单次短时垃圾回收 end startTime = currentTime -- 更新计时起点 end coroutine.yield() -- 使用协程让出CPU资源给其他任务 end end -- 启动一个独立线程来进行垃圾管理 local gcThread = coroutine.create(timedGarbageCollection) coroutine.resume(gcThread, 1, 5) -- 设置每隔一秒做五次小规模GC处理 print("Main program continues running...") for i=1,1e6 do end -- 主程序继续工作... ``` 上述代码片段创建了一个名为 `timedGarbageCollection` 的函数,它接受两个参数:间隔时间和每个间隔内的步数。此函数会在指定时间内多次调用 `collectgarbage("step")` 并利用协程确保不影响主线程的工作流程[^2]。 另外需要注意的是,在调整垃圾回收行为之前可能需要重新配置其性能参数比如暂停时间和乘法因子等,默认情况下这些值分别为 200 和 400 ,即允许 GC 占用最多 CPU 时间的一半,并且当已分配内存达到现有总量四倍时才启动新的收集过程[^3]。因此可以根据实际需求修改它们以便更好地配合手动调度策略: ```lua collectgarbage("setpause", 100) -- 减少停顿时间至最低限度 collectgarbage("setstepmul", 5000)-- 加快回收速度到正常水平五十倍以上 ``` 通过这种方式能够灵活定制适合特定应用场景下的自动或半自动化内存维护方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值