Skip to main content

GC算法与流程

面试题

  1. Lua 执行代码执行流程
  2. LuaGC 算法
  3. GC 经历了哪五个阶段?
  4. Lua5.4 GC 算法
  5. Lua 优化策略

Lua 代码执行流程

img


📊 Lua GC核心算法

三色增量标记清除

  • 三色标记法:将对象分为白、灰、黑三色:

  • 白色:初始状态,表示对象未被扫描,可回收

  • 灰色:中间状态,表示对象已被扫描但引用的子对象未扫描

  • 黑色:完成状态,表示对象及其引用链已扫描完毕,不可回收

  • 增量执行:GC过程拆分为小步骤,避免一次性扫描导致卡顿(Stop-The-World)。

工作流程图示

img


⚙️GC五阶段

初始化阶段(GCSpause)

  • 操作:从根节点(全局表、主线程栈、注册表)开始,将直接引用对象由白变灰,加入灰色链表。
  • 关键代码restartcollection(g)

传播标记阶段(GCSpropagate)

  • 操作:遍历灰色链表,将每个灰对象:

  • 引用的白对象变灰(加入链表)。

  • 自身变黑(移出链表)。

  • 增量控制:每次扫描一定数量对象后暂停,避免单帧卡顿。

原子阶段(GCSatomic)

  • 不可中断操作:处理剩余灰色对象(如grayagain链表)和弱表:

  • 弱表处理:若键/值为白色,则移除该条目。

  • 元方法调用:触发__gc方法(如Userdata的析构)。

  • 白色切换:将当前白色从WHITE0切换为WHITE1,新对象初始化为WHITE1,避免误删。

清扫阶段(GCSsweep & GCSsweepstring)

  • 字符串清扫:单独处理字符串池(因字符串无引用关系)。

  • 对象清扫:遍历所有对象:

  • 白色(旧白):回收内存。

  • 灰/黑色:重置为白色(WHITE0),等待下次GC。

收尾阶段(GCSfinalize)

  • 处理残留对象:如含__gc方法的Userdata,将其加入特殊链表,延迟到下次GC回收。

🛡️ 屏障机制:解决并发修改问题

增量GC过程中,若已标记对象(黑色)新增引用白色对象,需通过屏障保护:

  • 向前屏障(luaC_barrierf):直接将新引用的白对象标记为灰色(适用于Proto等稳定对象)。
  • 向后屏障(luaC_barrierback):将黑色父对象退灰,加入grayagain链表(适用于Table等频繁修改对象)。

图示示例

[黑色] --新增引用--> [白色对象]
↓ 触发向后屏障
[灰色] --加入grayagain--> [后续原子阶段处理]

Lua 5.4分代GC(可选模式)

核心思想

  • 分代假设:多数对象生命周期短,少数长期存活。
  • 晋升规则:存活两轮GC的对象晋升老年代,后续GC仅扫描新生代对象。

工作流程

img

风险

  • 老年代泄漏:若老年代对象引用已失效的新生代对象,需周期性全量GC补偿。

优化策略

  1. 避免临时对象:减少循环内创建表(如{}),改用对象复用。
  2. 手动控制GC
collectgarbage("stop")  -- 关键逻辑前暂停GC
-- 执行高性能代码
collectgarbage("step") -- 分步触发GC
  1. 弱表管理:对缓存数据使用弱引用,避免阻止回收:
local cache = setmetatable({}, {__mode = "v"}) -- 值弱引用
  1. 分帧回收:在Update中分批次调用collectgarbage("step"),避免单帧卡顿。
  2. 触发时机:在场景切换时主动GC,减少实时帧压力。
  3. 内存监控:使用LuaProfiler定位高频生成临时对象的位置。

GC机制对比

机制三色标记清除分代GC(Lua 5.4)
适用场景内存敏感型实时应用对象生命周期差异大场景
卡顿风险增量执行,卡顿低全量GC时仍有卡顿
内存开销无分代记录开销需维护老年代链表
回收完整性每次全量扫描可能遗漏跨代引用