tibaiwan1888 发表于 2026-6-5 00:11:21

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


qq154886255 发表于 2026-6-5 10:11:04

多发点ai写的东西

jz9005 发表于 2026-6-5 10:25:24

快了快了,马上就能用VV了

tibaiwan1888 发表于 2026-6-5 13:45:34


快了快了,马上就能用VV了

jianchao9488 发表于 2026-6-5 19:23:06

快了快了,马上就能用VV了

牛头 发表于 2026-6-17 14:55:10

-- 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

我爱嘟嘟 发表于 2026-6-17 20:41:59

看不懂呀

pxlovewj 发表于 2026-6-19 21:05:49

快了快了,马上就能用VV了

son3327 发表于 2026-6-27 02:07:13

加油啊加啊

son3327 发表于 2026-6-27 17:16:36

学习学习
页: [1] 2
查看完整版本: VVM2服务端Lua内存泄漏:常见原因 + 自制检测工具