强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Lua 从入门到精通 / 14 - 垃圾回收 / Garbage Collection

垃圾回收 / Garbage Collection

Lua 使用自动内存管理。你创建对象,Lua 负责回收不再使用的对象。理解 GC 机制可以帮你写出更高效的代码。

Lua uses automatic memory management. You create objects, Lua reclaims unused ones. Understanding GC helps you write more efficient code.


🟢 基础 / Basics

1. GC 基本概念

-- Lua 的 GC 是追踪式(tracing)垃圾回收
-- 原理:从根对象出发,标记所有可达对象,回收不可达对象

-- 根对象(root set)包括:
-- - 全局表 _G
-- - 注册表(registry)
-- - 所有正在执行的协程栈
-- - 所有 userdata 的 metatable

-- 不可达的对象 = 垃圾,会被回收
local t = {data = "big data"}
t = nil    -- 原来的 table 不再可达,可被 GC

-- collectgarbage() 控制 GC
collectgarbage("collect")    -- 强制执行一次完整的 GC 周期
collectgarbage("count")      -- 返回当前内存使用量(KB)
print(collectgarbage("count"))  -- 例如: 23.5

2. GC 控制 / GC Control

-- collectgarbage(opt, arg)

collectgarbage("collect")   -- 执行一次完整的 GC
collectgarbage("count")     -- 返回内存使用量 (KB)
collectgarbage("restart")   -- 重启自动 GC
collectgarbage("stop")      -- 停止自动 GC
collectgarbage("step", 100) -- 执行一步 GC(参数越大工作量越多)
collectgarbage("setpause", 200)  -- 设置暂停参数(默认200,=200%)
collectgarbage("setstepmul", 200) -- 设置步进乘数(默认200)

-- 查看内存使用
local before = collectgarbage("count")
local t = {}
for i = 1, 100000 do t[i] = i end
local after = collectgarbage("count")
print(string.format("Memory: %.1f KB → %.1f KB", before, after))
collectgarbage("collect")
local cleaned = collectgarbage("count")
print(string.format("After GC: %.1f KB", cleaned))

🟡 进阶 / Intermediate

1. 弱引用表 / Weak Tables

-- 弱引用表允许 GC 回收其中被弱引用的对象
-- "k" = 键弱引用, "v" = 值弱引用, "kv" = 都弱引用

-- 缓存模式:不阻止 GC
local cache = setmetatable({}, {__mode = "v"})
local function getExpensiveData(id)
    if not cache[id] then
        cache[id] = {id = id, data = string.rep("x", 10000)}
    end
    return cache[id]
end

getExpensiveData(1)
getExpensiveData(2)
print(collectgarbage("count"))  -- 较高
collectgarbage("collect")
print(collectgarbage("count"))  -- 缓存中的数据还在(有引用)

-- 如果外部不再引用这些数据:
-- cache 中的值是弱引用,GC 可以回收

2. __gc 元方法 / Finalizer

-- __gc 在对象被 GC 回收前调用
local mt = {
    __gc = function(self)
        print("Finalizing: " .. tostring(self.name))
    end
}

do
    local obj = setmetatable({name = "resource"}, mt)
    print("Created: " .. obj.name)
end  -- obj 超出作用域

collectgarbage("collect")  -- 触发 __gc
-- 输出: Finalizing: resource

-- 注意事项:
-- 1. __gc 的执行顺序不确定
-- 2. __gc 中不应抛出错误
-- 3. __gc 中引用的对象不会被回收
-- 4. 不要依赖 __gc 来释放关键资源(用显式 close)

3. GC 性能调优 / GC Tuning

-- 默认参数适合大多数场景
-- 但如果应用有特殊的内存使用模式,可以调优

-- 场景一:大量临时对象(游戏帧循环)
collectgarbage("setpause", 100)     -- 更积极地 GC
collectgarbage("setstepmul", 400)   -- 每步做更多工作

-- 场景二:大量长生命周期对象(服务器)
collectgarbage("setpause", 300)     -- 更少地 GC
collectgarbage("setstepmul", 100)   -- 每步做更少工作

-- 场景三:实时系统(避免 GC 暂停)
collectgarbage("stop")              -- 停止自动 GC
-- 手动在合适时机触发:
function onIdle()
    collectgarbage("step", 10)      -- 做一点 GC 工作
end

🔴 高级 / Advanced

1. 三色标记算法 / Tri-color Marking

Lua GC 使用三色标记算法:

白色 (White): 未被访问的对象(可能是垃圾)
灰色 (Gray):  已被访问但子对象未处理
黑色 (Black): 已被访问且所有子对象已处理

标记阶段(Mark Phase):
1. 根对象标记为灰色
2. 取一个灰色对象,标记为黑色
3. 将其所有子对象标记为灰色
4. 重复直到没有灰色对象
5. 所有白色对象 = 垃圾

清扫阶段(Sweep Phase):
6. 遍历所有对象
7. 回收所有白色对象
8. 将黑色对象重置为白色(准备下一轮)

增量 GC(Incremental GC):
- 不是一次性完成,而是分步执行
- 每步做一部分标记/清扫工作
- 避免长时间的 GC 暂停

2. Lua 5.4 分代 GC / Generational GC (Lua 5.4+)

-- Lua 5.4 新增了分代 GC 模式
-- 原理:大多数对象"朝生夕灭"(generational hypothesis)

collectgarbage("generational")    -- 切换到分代模式
collectgarbage("incremental")    -- 切换回增量模式(默认)

-- 分代 GC 的优点:
-- - 年轻代频繁收集(回收短命对象效率高)
-- - 老年代较少收集(减少对长命对象的扫描)
-- - 整体 GC 暂停更短

-- 分代 GC 参数
collectgarbage("setminorpause", 20)   -- 次要收集间隔
collectgarbage("setmajorminorpause", 100)  -- 主要收集间隔

-- 何时使用分代 GC:
-- - 大量短生命周期对象(如 Web 请求处理)
-- - 较少的长生命周期对象
-- - 对延迟敏感的应用

3. 常见的内存泄漏模式 / Common Memory Leak Patterns

-- 泄漏一:全局表持续增长
local globalCache = {}
function process(id)
    globalCache[id] = {data = "big"}    -- 永远不会被回收!
end

-- 修复:使用弱引用表
globalCache = setmetatable({}, {__mode = "v"})

-- 泄漏二:闭包引用了大对象
local function leak()
    local bigData = string.rep("x", 1000000)
    return function()
        return #bigData    -- 闭包持有 bigData 的引用
    end
end

-- 修复:只捕获需要的值
local function noLeak()
    local bigData = string.rep("x", 1000000)
    local size = #bigData    -- 只捕获数字
    return function()
        return size
    end
end

-- 泄漏三:循环引用
-- 注意:Lua 的 GC 可以处理循环引用!这不是真正的泄漏
-- 但如果配合 __gc,可能会阻止 GC

小结 / Summary

层级你需要知道的 / What You Need to Know
🟢 基础自动 GC、collectgarbage(“count”/“collect”)、内存管理基本概念
🟡 进阶弱引用表(__mode)、__gc 析构器、GC 参数调优
🔴 高级三色标记算法、增量 GC、分代 GC (5.4+)、内存泄漏模式

下一章:调试库 / Debug Library