|
|
-- MemoryWatcher.lua
-- 定期采样内存,输出增长趋势,帮助定位泄漏点
local M = {}
local _snapshots = {}
local _trackers = {} -- 追踪特定表的大小变化
-- 采样一次(依赖引擎提供的内存查询接口)
function M.snapshot(label)
local kb = collectgarbage("count")
table.insert(_snapshots, {
label = label or ("t" .. #_snapshots + 1),
kb = kb,
time = os.time(),
})
return kb
end
-- 打印增长报告
function M.report()
if #_snapshots < 2 then
print("[MemWatcher] 至少需要2次采样")
return
end
local base = _snapshots[1]
print(string.format("%-12s %8s %8s", "标签", "KB", "增长KB"))
print(string.rep("-", 32))
for _, s in ipairs(_snapshots) do
print(string.format("%-12s %8.1f %+8.1f",
s.label, s.kb, s.kb - base.kb))
end
end
-- 追踪一个全局表的元素数量变化(找哪个表在无限增长)
function M.track(name, tbl)
_trackers[name] = tbl
end
function M.checkTrackers()
for name, tbl in pairs(_trackers) do
local count = 0
for _ in pairs(tbl) do count = count + 1 end
print(string.format("[Track] %-20s 元素数: %d", name, count))
end
end
配合定时器使用:
local MW = require("MemoryWatcher")
-- 追踪可疑的全局表
MW.track("PlayerCache", PlayerCache)
MW.track("EventListeners", _listeners)
-- 每5分钟采样一次
local tick = 0
SetTimer(300 * 1000, function()
tick = tick + 1
MW.snapshot("t" .. tick .. "(" .. tick*5 .. "min)")
MW.checkTrackers()
if tick % 6 == 0 then -- 每30分钟打印一次报告
MW.report()
end
end)
-- ❌ 泄漏1:事件监听器只注册不注销
-- 玩家下线后 listener 仍挂在全局表里
EventBus.on("player_die", function() ... end) -- 没有对应的 off
-- ❌ 泄漏2:用玩家名做key缓存,玩家下线后不清理
_cache[playerName] = bigObject -- 永远不删
-- ❌ 泄漏3:闭包意外捕获大对象
local bigTable = loadAllItems() -- 几千条数据
SetTimer(1000, function()
-- 只用了 bigTable 的一个字段,但整个 bigTable 被闭包持有
print(bigTable.count)
end)
-- ✅ 正确做法:只捕获需要的值
local count = loadAllItems().count
SetTimer(1000, function() print(count) end)
return M
|
|