On this page

Lua 垃圾回收

Lua 垃圾回收机制详解

Lua 使用自动内存管理机制,通过垃圾回收(Garbage Collection,GC)来释放不再使用的内存。以下是 Lua 垃圾回收的全面解析:

1. 垃圾回收基础

工作原理

Lua 使用标记-清除(Mark-and-Sweep)算法:

  1. 标记阶段:从根对象(全局变量、栈、注册表等)出发,标记所有可达对象
  2. 清除阶段:遍历所有对象,回收未被标记的对象内存

回收对象类型

  • 表(table)
  • 函数(function)
  • 字符串(string)
  • 用户数据(userdata)
  • 线程(thread)

2. 控制垃圾回收

手动控制函数

-- 执行完整的垃圾回收周期
collectgarbage("collect")  -- 返回已回收内存(KB)

-- 停止自动垃圾回收
collectgarbage("stop")

-- 重启自动垃圾回收
collectgarbage("restart")

-- 执行单步垃圾回收
collectgarbage("step")  -- 返回布尔值表示是否完成

-- 获取当前内存使用量(KB)
local mem = collectgarbage("count")

-- 设置垃圾回收器暂停时间(默认200)
collectgarbage("setpause", 100)

-- 设置垃圾回收器步进倍率(默认200)
collectgarbage("setstepmul", 200)

3. 垃圾回收器参数

参数默认值描述
pause200控制回收频率,值越小回收越频繁(100表示内存使用翻倍时触发回收)
stepmul200控制回收速度,值越大每次回收操作处理的内存越多
stepsize-每次回收操作的内存大小(KB),Lua 5.1+支持

4. 弱表(Weak Table)

弱表是一种特殊的表,不会阻止其键/值被回收:

创建弱表

-- 键弱引用,值强引用
local weakKeys = setmetatable({}, {__mode = "k"})

-- 值弱引用,键强引用
local weakValues = setmetatable({}, {__mode = "v"})

-- 键和值都弱引用
local weakBoth = setmetatable({}, {__mode = "kv"})

使用场景

  1. 缓存系统

    local cache = setmetatable({}, {__mode = "v"})
       
    function getBigData(id)
        if cache[id] then
            return cache[id]
        end
        local data = createExpensiveData(id)
        cache[id] = data
        return data
    end
    
  2. 对象属性管理

    local properties = setmetatable({}, {__mode = "k"})
       
    function setProperty(obj, name, value)
        if not properties[obj] then
            properties[obj] = {}
        end
        properties[obj][name] = value
    end
    

5. 终结器(__gc元方法)

Lua 5.2+ 支持对象被回收时执行清理操作:

local File = {}

function File.new(filename)
    local f = {name = filename}
    setmetatable(f, {
        __gc = function(self)
            print("关闭文件:", self.name)
            -- 这里可以添加实际的清理代码
        end
    })
    return f
end

do
    local temp = File.new("test.txt")
end  -- 离开作用域后,__gc会被调用

6. 内存泄漏检测

常见泄漏场景

  1. 全局变量意外创建

    function leak()
        globalVar = "oops"  -- 意外创建全局变量
    end
    
  2. 闭包保持不必要引用

    local function createLeak()
        local bigData = getHugeData()
        return function()
            -- 即使不需要bigData,闭包也保持引用
            print("leaking")
        end
    end
    
  3. 循环引用

    local a = {}
    local b = {ref = a}
    a.ref = b  -- 循环引用
    

检测方法

-- 定期检查内存增长
local lastMem = collectgarbage("count")
setInterval(function()
    local current = collectgarbage("count")
    if current - lastMem > 100 then  -- 增长超过100KB
        print("可能的内存泄漏:", current - lastMem, "KB")
    end
    lastMem = current
end, 10)  -- 每10秒检查一次

7. 性能优化建议

  1. 避免频繁创建临时表

    -- 不好
    for i = 1, 1000 do
        local t = {x = i, y = i*2}
        process(t)
    end
       
    -- 好
    local t = {}
    for i = 1, 1000 do
        t.x, t.y = i, i*2
        process(t)
    end
    
  2. 重用字符串:Lua 会内部化(intern)字符串,相同内容的字符串只存一份

  3. 大内存操作后手动回收

    processBigData()
    collectgarbage("collect")
    
  4. 合理设置GC参数

    -- 对延迟敏感的应用
    collectgarbage("setpause", 50)   -- 更频繁回收
    collectgarbage("setstepmul", 300) -- 更快回收
    

8. Lua 版本差异

特性Lua 5.1Lua 5.2+
增量GC支持
__gc元方法仅userdata所有对象
分代GCLua 5.4实验性支持
stepsize参数支持

Lua 的垃圾回收机制虽然自动运行,但理解其原理和调控方法对优化程序性能至关重要,特别是在内存受限或需要高性能的场景下。