前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >lua内存泄漏检测工具原理及设计

lua内存泄漏检测工具原理及设计

作者头像
车雄生
发布2021-11-10 14:32:53
2.1K0
发布2021-11-10 14:32:53
举报
文章被收录于专栏:咩嗒

Google一下“lua内存泄漏检测”,基本都是直接或间接指向云风多年前写的《一个 Lua 内存泄露检查工具》,其思路是给虚拟机做个快照,记录下所有gc对象地址及引用关系,然后通过对比两次快照来分析内存泄漏情况。文章似乎把内存泄漏等同于某个gc对象的新增了。

然而,新增gc对象就代表内存泄漏?看下这段代码:

代码语言:javascript
复制
local no_leak = {}
function innocent()
    no_leak.a = {x = 1}
    no_leak.b = {y = 1}
end

innocent函数每次执行都会新增两个table并持有它们,但这明显不是内存泄漏,而且这是很常见的写法。

不新增gc对象就代表没内存泄漏?也不是:

代码语言:javascript
复制
local local_leak = {}
function make_leak()
    table.insert(local_leak, 1)
end

这种泄漏文章提供的工具貌似就无能为力。它只记录gc对象及gc对象间的引用关系。但数字不是gc对象。

带GC语言的内存泄漏

C/C++这类语言的内存泄漏,是分配了内存忘了释放,但GC会帮我们自动释放这类内存。而在带GC的语言的内存泄漏,则是往一个容器里头塞东西忘了删掉。

往一个容器里头塞东西忘了删掉会导致什么现象?

当然是导致这容器变大,所以疑似内存泄漏检测就变成了容器大小(是否递增)检测。

这在lua里头又特别简单,因为。。lua只有一种容器--table。

lua内存泄漏检查

核心代码十分简单,只有十来行C代码:

代码语言:javascript
复制
typedef void (*TableSizeReport) (const void *p, int size);
LUA_API void xlua_report_table_size(lua_State *L, TableSizeReport cb, int fast)
{
	GCObject *p = G(L)->allgc;
	while (p != NULL)
	{
		if (p->tt == LUA_TTABLE)
		{
			Table *h = gco2t(p);
			cb(h, table_size(h, fast));
		}
		p = p->next;
	}
}

遍历所有对象,如果是table,则把指针和size报告给调用者。

这个C代码将由C#调用,并记录下table的size信息,也灰常简单:

代码语言:javascript
复制
static Data getSizeReport(LuaEnv env)
{
    Data data = new Data();
    data.Memroy = env.Memroy;

    LuaDLL.Lua.xlua_report_table_size(env.L, (IntPtr p, int size) => {
        data.TableSizes.Add(p, size);
    }, 0);

    return data;
}

好了,接下来对比前后size信息,就可以找出可能存在内存泄漏的table的指针了,这里就不贴代码了,文章中所有代码都可以在xLua开源项目中找到。

table详细信息

光拿table的指针是没啥用的,我们要得到更多信息才定位。

一般而言,table顺其引用链往上找,都能归结到三个地方:

1、upvalue,比如你在lua脚本定义的local xx = {};

2、全局变量;

3、c侧共用的一个特殊table:registry;

当然,栈也可能引用table,但我们是在C#调用C代码,当时没跑lua,栈应该是空的,而且仅仅栈指向的对象,我们可以先不管,这对象要么是临时的,要么后面还是被上面三个地方引用。

table详细信息思路

1、获取对象引用关系,生成反向索引表;

2、从反向索引表查找到疑似泄漏table,然后根据反向索引往上找,一直找到上述的三个根,生成路径

一个典型泄漏信息报告是这样的:

代码语言:javascript
复制
total memroy: 87
potential leak(180) in {leak2.lua:local anthor_leak.a[1].b,_R.ref_anthor_leak.a[1].b}
potential leak(181) in {_G.global_leak}
potential leak(180) in {leak1.lua:local local_leak}

第一个行表示有个大小为180的疑似泄漏table,它被两个地方引用了

一个是leak2.lua文件的局部变量anthor_leak,位于这个局部变量的a[1].b子节点

一个是registry表(上面的第三个地方),ref_anthor_leak.a[1].b子节点

快泄漏和慢泄漏

如果程序中存在一个泄漏很快以及一个泄漏很慢的地方,我们两次对比table size信息,很可慢的因为没涨而被无视。

这也没关系,当你找到泄漏快的地方,解决了快的,慢的就能被检查出来了。

测试例子也展示这这种情况。

就说这么多,更详细的情况看代码就好了:

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档