luinterface内存泄漏问题
本文关键字:问题 泄漏 内存 luinterface | 更新日期: 2023-09-27 18:16:31
我发现我在c#/Lua luinterface项目中有一个糟糕的内存泄漏。我用c#编写了一个简单的测试函数,它每0.5秒从Lua循环调用一次。我可以看到Lua内存使用量随着每次循环而增加。我最新的c#代码是
public LuaTable testMemTable()
{
LuaTable tabx = m_lua.GetTable("tabx");
if (tabx != null)
{
tabx.Dispose();
tabx = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
m_lua.NewTable("tabx");
tabx = m_lua.GetTable("tabx");
for (int i = 0; i < 20000; i++)
tabx[i] = i * 10;
return tabx;
}
尽管执行tabx. dispose()和tabx=null,然后强制GC,我仍然看到内存没有被释放。没有luinterface函数来释放先前分配的表,所以我不知道我还能做些什么来释放内存?
Lua端代码非常简单
while true do
myAPILibs.testMemTable()
if tabx ~= nil then
print(string.format("Size = %d", #tabx))
else
print(string.format("Size = nil"))
end
print(tabx, tabx[111])
myAPILibs.sleep(500)
print("Before ", collectgarbage("count") * 1024)
collectgarbage("collect")
print("After ", collectgarbage("count") * 1024)
end
任何帮助解决我的内存泄漏问题将是非常感激的。
再次表示感谢Geoff
我需要抱怨一下luinterinterface,因为它使这个问题很难解决——在某些情况下,不泄漏内存甚至是不可能的。
luinterface(和NLua)中的CLR对象代理不提供释放Lua引用的终结器。1你必须处理每一个引用Lua对象的CLR对象。如果您忘记了一次,Lua对象将永远无法被垃圾收集。更糟糕的是,您不能正确地处理从Lua调用的CLR方法返回的引用。如果你在返回引用之前处理了它,它就消失了,你不能再返回它了。如果不处理它,CLR引用就会泄露。您可以做一些代码操作来正确地处理引用,但是没有任何理由要求您做这种工作。
现在让我离开我的临时演说箱,来谈谈你的代码。
在你所有的方法中有很多地方会泄漏这些对象。你对GetTable()
的使用掩盖了你对其工作原理的误解。每次调用此方法时,都会得到一个新的 CLR对象,该对象引用由该Lua全局变量引用的Lua表。您对它的使用似乎假设您可以执行m_lua.GetTable("tabx").Dispose()
并完成某些操作——这所做的只是创建一个新的CLR引用,然后仅处理那个引用。换句话说,什么都不做是一种代价高昂的方法。
每次调用GetTable()
都应该有一个相应的处置(或者使用c#的using
块为你做处置)。
LuaTable
对象并不会破坏Lua表!它所做的只是释放那个特定的 CLR对表的引用。
这适用于所有引用Lua对象的 luinterinterface类型,包括函数,这是另一个可能发生泄漏的地方。
这里我将展示每个方法的问题,以及如何重写它来解决这些问题。
public LuaTable testMemTable()
{
// This code does nothing. You get a new reference to the Lua table
// object, and then you immediately dispose it. You can remove this
// whole chunk of code; it's a really expensive no-op.
LuaTable tabx = m_lua.GetTable("tabx");
if (tabx != null)
{
tabx.Dispose();
tabx = null;
GC.Collect();
GC.WaitForPendingFinalizers();
}
m_lua.NewTable("tabx");
// You create a new CLR object referencing the new Lua table, but you
// don't dispose this CLR object.
tabx = m_lua.GetTable("tabx");
for (int i = 0; i < 20000; i++)
tabx[i] = i * 10;
return tabx;
}
正确的方法是:
public void testMemTable()
{
m_lua.NewTable("tabx");
using (LuaTable tabx = m_lua.GetTable("tabx")) {
for (int i = 0; i < 20000; i++) {
tabx[i] = i * 10;
}
}
}
(注意,我将返回类型更改为void,因为您从不使用返回值。)
public LuaTable getNewTableCSharp()
{
// You don't dispose the function object.
var x = lua.GetFunction("getNewTableLua");
// You don't dispose the table object.
var retValTab = (LuaTable)x.Call()[0];
return retValTab;
}
注意,由于getNewTableLua
函数永远不会被重新赋值,因此实际上并没有泄漏Lua函数,但是每次调用该函数时,您都泄漏了Lua表中的一个槽,该槽保存了对该函数的引用。
现在问题来了:因为这个函数是从Lua调用的,并且返回一个对Lua对象的引用,你不能修复两个泄漏,你只能修复函数泄漏:
public LuaTable getNewTableCSharp()
{
using (var x = lua.GetFunction("getNewTableLua")) {
// Still leaks, but you can't do anything about it.
return (LuaTable)x.Call()[0];
}
}
回到我的临时演讲,考虑使用Eluant,它是CLR的一组Lua绑定(很像luinterface),除了它解决了内存管理问题。(免责声明:我是Eluant的作者。)
特别地,它解决了你在这里遇到的问题:
- Eluant的引用Lua对象的CLR对象有终结器,它将把Lua引用排队等待稍后释放。如果您忘记处理引用Lua对象的CLR对象,那么它最终还是会被收集。(但是你仍然应该尽快处理引用,最好使用c#的
using
块,以确保Lua的GC能够及时收集对象。) - 如果你在Lua调用的方法中返回一个CLR对象对Lua对象的引用,Eluant会在将控制权返回给Lua之前为你处理这个引用。
1看这里。结束器是存在的,但是处理程序不释放Lua对象引用,如果它被结束器调用。换句话说,终结器基本上什么都不做。请注意,由于Lua不是线程安全的,因此此时实际释放Lua引用是不安全的,但是释放操作可以排队等待以后执行。luinterface不这样做;洗提液。
简短回答
将lua.newTable("testTable")
替换为lua.doString("for key,value in ipairs(testTable) do testTable[key]=nil end");
没有内存泄漏,你的代码可以正常工作。
长回答假设你有一个像这样的lua脚本:
newTable = {1,2,3,4,5,6,7,8,9,10,11,12,13,14}
newTable = {}
如果你在Lua端这样做,即使你从来没有真正清除原始表,也不会有泄漏。在重新分配表之前,Lua不知何故知道要先释放表。
然而,如果在将表设置为空表之前有任何c#引用,即使lua处理了它的内容,c#引用也不会被处理;显然,它的行为就好像实例创建了一个全新的表。重置表需要做的是:
for key,value in ipairs(testTable) do
testTable[key] = nil
end
你的问题不是一个真正的内存泄漏,这是你的静态Lua实例正在加载表的多个版本,因为它被重置的方式。