VVM2服务端Lua内存泄漏:常见原因 + 自制检测工具
-- 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(" 至少需要2次采样")
return
end
local base = _snapshots
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 = 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(" %-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 = 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
多发点ai写的东西 快了快了,马上就能用VV了
快了快了,马上就能用VV了 快了快了,马上就能用VV了 -- MemoryWatcher.lua 修复优化版
local M = {}
local _snapshots = {}
local _trackers = {} -- key:表名, value:{tbl=目标表, lastCount=上次元素数}
local _maxSnapShot = 200 -- 最多保留200条快照,防止无限膨胀
local _enable = true -- 监控总开关
-- 内部执行完整GC再采样,返回当前内存KB
local function getCleanMemKB()
collectgarbage("collect") -- 完整垃圾回收
return collectgarbage("count")
end
-- 采样一次
function M.snapshot(label)
if not _enable then return 0 end
local kb = getCleanMemKB()
local nowSec = os.time()
local idx = #_snapshots + 1
local autoLabel = string.format("t%d_%s", idx, os.date("%H:%M:%S", nowSec))
label = label or autoLabel
table.insert(_snapshots, {
label = label,
kb = kb,
time = nowSec,
clock = os.clock()
})
-- 超出上限自动清理旧快照
if #_snapshots > _maxSnapShot then
table.remove(_snapshots, 1)
end
return kb
end
-- 输出完整报告:相对初始基准 + 相邻间隔增量
function M.report()
if not _enable then return end
if #_snapshots < 2 then
print(" 至少需要2次采样才能生成报告")
return
end
local base = _snapshots
print("\n===== 内存增长报告 =====")
print(string.format("%-15s %8s %8s %8s %10s", "标签", "KB", "总增长KB", "间隔增长KB", "间隔秒"))
print(string.rep("-", 50))
local prev = base
for _, s in ipairs(_snapshots) do
local totalInc = s.kb - base.kb
local stepInc = s.kb - prev.kb
local diffSec = s.time - prev.time
print(string.format(
"%-15s %8.1f %+8.1f %+8.1f %10d",
s.label, s.kb, totalInc, stepInc, diffSec
))
prev = s
end
print("========================\n")
end
-- 追踪指定表,自动记录历史数量
function M.track(name, tbl)
if tbl == nil then
print(string.format(" 追踪失败 %s:目标表为空", name))
return
end
-- 避免重复覆盖提示
if _trackers then
print(string.format(" 警告:覆盖已有追踪表 %s", name))
end
-- 不直接存原始表弱引用,降低常驻风险
local count = 0
for _ in pairs(tbl) do count = count + 1 end
_trackers = {tbl = tbl, lastCount = count}
end
-- 检查所有追踪表,输出数量+变化差值
function M.checkTrackers()
if not _enable then return end
print("\n 表元素监控:")
for name, info in pairs(_trackers) do
local tbl = info.tbl
local curr = 0
for _ in pairs(tbl) do curr = curr + 1 end
local diff = curr - info.lastCount
print(string.format(
"%-20s 当前数量:%6d 变化:%+6d",
name, curr, diff
))
info.lastCount = curr
end
print()
end
-- 控制接口:启停、清理数据
function M.setEnable(flag) _enable = flag end
function M.clearAll()
_snapshots = {}
_trackers = {}
end
return M 看不懂呀 快了快了,马上就能用VV了 加油啊加啊 学习学习
页:
[1]
2