17.垃圾回收
17.1 知识点
垃圾回收的基本概念
- Lua 有自动垃圾回收机制,不需要像 C/C++ 那样手动
malloc/free。 - Lua 会自动管理自己创建出来的对象,比如字符串、表、函数、userdata、thread 等。
- 一个对象如果还能从全局变量、局部变量、函数调用栈、表字段等地方访问到,就可以先理解为可达。
- 一个对象如果已经没有任何地方能访问到,就可以先理解为不可达,后续就可能被 GC 回收。
- GC 回收的是不可达对象,不只是有没有互相引用。
这里容易有一个误区:Lua 主流 GC 不是引用计数。
所以纯 Lua 对象就算互相引用,只要整个引用环已经从外部访问不到,GC 仍然可以把它们回收掉。
local a = {}
local b = {}
a.other = b
b.other = a
a = nil
b = nil
-- a 和 b 原来互相引用
-- 但外部变量已经访问不到它们了
-- 后续 GC 仍然可以回收这两个表
弱表、__gc、增量式 GC、分代 GC 可以后面在学习。弱表的核心是不让缓存表成为强引用根。
垃圾回收方法
- 垃圾回收相关方法是
collectgarbage("命令")。 - 先掌握两个最常用的:
collectgarbage("count")collectgarbage("collect")
collectgarbage("count") 获取当前 Lua 占用的内存数
collectgarbage("count")返回当前 Lua 占用的内存,单位是 KB。- 如果想粗略换算成字节,可以用返回值乘以 1024。
- 这个值适合用来观察脚本内存变化,不要把它当成特别精确的系统级内存统计。
print(collectgarbage("count")) -- 20.8671875
collectgarbage("collect") 手动触发一次完整垃圾回收
collectgarbage("collect")会手动触发一次完整 GC。- 主动让 Lua 尝试回收已经不可达的对象。
- 不建议在游戏运行热路径里频繁调用完整 GC,不然容易造成卡顿。
collectgarbage("collect")
print(collectgarbage("count")) -- 19.771484375
简单感受垃圾回收
- 把 Lua 对象置空后,如果没有别的引用再指向它,这个对象后续就可以被 GC 回收。
- 注意:置空不等于马上释放,只是让对象变成“可以被回收”。
- 具体什么时候回收,由 Lua 的 GC 机制决定。
- 如果手动调用
collectgarbage("collect"),count 会掉,就可以更直观看到内存变化。
test = {
id = 1,
name = "123123123123123123",
age = 10,
}
print(collectgarbage("count")) -- 19.9560546875,创建表后多占用了一些内存
test = nil
collectgarbage("collect")
print(collectgarbage("count")) -- 19.771484375,对象不可达后,被 GC 回收了一部分内存
这里和 C# 的 GC 有点像:切断引用后交给 GC 管而非手动管理释放。
但 Lua 和 C# 是两套不同的运行时,实现细节还是有差异的。
什么时候手动垃圾回收?
- Lua 本身会自动进行垃圾回收。
- 正常情况下,不需要每创建几个对象就手动
collectgarbage("collect")。 - 不要在战斗、角色移动、UI 高频刷新这类热路径里频繁手动完整 GC。
- 如果确实要手动触发,一般放在切场景、退出副本、关闭大型界面、加载完成后的这类相对不敏感的时机。
- 优化 GC 的常见思路:减少临时对象创建。比如少在 Update 里频繁 new 表、少做没必要的字符串拼接、缓存一些可复用数据。GC 是兜底机制,不应该拿它当主要优化手段。
总结
- Lua 有自动垃圾回收机制。
- 字符串、表、函数、userdata、thread 等 Lua 对象都会被 GC 管理。
- GC 判断对象是否能回收,核心看对象是否还能被访问到。
collectgarbage("count")可以查看当前 Lua 内存占用,单位是 KB。collectgarbage("collect")可以手动触发一次完整 GC。- 不建议频繁手动 full GC,尤其不要放在游戏频繁调用的地方。
- 增量式 GC、分代 GC、弱表、
__gc、GC 参数、泄漏排查这些内容。
17.2 知识点代码
Lesson17_垃圾回收.lua
print("**********垃圾回收************")
print("**********知识点一 垃圾回收方法************")
-- 垃圾回收相关方法 collectgarbage("命令"),通过传入不同命令进行不同操作
-- collectgarbage("count")
-- 获取当前 Lua 占用的内存数,单位是 KB
-- 如果想粗略换算成字节,可以用返回值 * 1024
print(collectgarbage("count")) -- 20.8671875
-- collectgarbage("collect")
-- 手动触发一次完整垃圾回收
collectgarbage("collect")
print(collectgarbage("count")) -- 19.771484375,少占用了一些内存
print("**********知识点二 简单感受垃圾回收************")
-- Lua 有自动垃圾回收机制
-- 对象如果已经没有任何地方能访问到,后续就可能被 GC 回收
-- 注意:置空不等于马上释放,只是让对象变成“可以被回收”
test = {
id = 1,
name = "123123123123123123",
age = 10,
}
print(collectgarbage("count")) -- 19.9560546875,创建表后多占用了一些内存
test = nil
collectgarbage("collect")
print(collectgarbage("count")) -- 19.771484375,对象不可达后,被 GC 回收了一部分内存
print("**********知识点三 可达和不可达************")
-- GC 回收的是不可达对象,不是简单看有没有互相引用
-- Lua 主流 GC 不是引用计数
-- 纯 Lua 对象形成引用环,只要整个环从外部访问不到,后续仍然可以被 GC 回收
local a = {}
local b = {}
a.other = b
b.other = a
a = nil
b = nil
collectgarbage("collect")
-- a 和 b 原来互相引用
-- 但外部变量已经访问不到它们了
-- 后续 GC 仍然可以回收这两个表
print("**********知识点四 自动垃圾回收和手动垃圾回收************")
-- Lua 本身会自动进行垃圾回收
-- 正常情况下,不需要每创建几个对象就手动 collectgarbage("collect")
-- 不建议在战斗、角色移动、UI 高频刷新这类热路径里频繁手动完整 GC
-- 如果确实要手动触发,一般放在切场景、退出副本、关闭大型界面、加载完成后的空档期
-- 增量式 GC、分代 GC、弱表、__gc、GC 参数、泄漏排查
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com