17.垃圾回收

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

×

喜欢就点赞,疼爱就打赏