LuaInterface 内存泄漏问题

3

我发现在一个C#/Lua LuaInterface项目中存在着内存泄漏问题。我已经在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,我仍然看到内存没有被释放。由于没有LuaInterface函数可以释放先前分配的表格,所以我不知道还能做什么来释放内存?
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

2个回答

2
我需要对LuaInterface进行一些抨击,因为它使得这个问题很难解决 - 在某些情况下,甚至不可能避免内存泄漏。
LuaInterface(和NLua)中的CLR对象代理没有提供释放Lua引用的终结器。您必须处理每个引用Lua对象的CLR对象。如果您有所遗漏,Lua对象就永远无法被垃圾回收。
更糟糕的是,您无法正确地处理从Lua调用的CLR方法返回的引用。如果在返回之前处理引用,则引用会丢失,再也不能返回它了。如果不处理它,则会泄漏CLR引用。您可以做一些代码技巧来正确处理引用,但这完全没有必要。
现在让我离开我的道德高地并处理您的代码。
在所有方法中,都存在许多地方泄漏这些对象。您使用GetTable()表明您对其工作方式的误解。每次调用此方法时,您都会获得一个新的CLR对象,该CLR对象引用由Lua全局变量引用的Lua表。您的用法似乎假定您可以执行m_lua.GetTable(“tabx”).Dispose(),并且可以完成某些操作 - 所有这样做的只是创建一个新的CLR引用,然后处置仅限于那个引用。换句话说,这是一个什么都不做的昂贵方式。
每次调用GetTable()都应该有相应的处置(或使用C#的using块来自动处置)。
要澄清的是,处理CLR LuaTable对象并不会销毁Lua表!它只是释放对该特定CLR引用表的引用。
对于每个引用Lua对象的LuaInterface类型,包括函数,在这里您将展示每个方法的问题以及如何重写它们以解决这些问题。
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绑定集(类似于LuaInterface),但它解决了内存管理问题。(免责声明:我是Eluant的作者。)
特别是,它解决了您在这里遇到的问题:
- Eluant的CLR对象引用Lua对象具有终结器,将排队等待释放Lua引用。如果您忘记处理引用Lua对象的CLR对象,它最终仍会被收集。(但最好尽快处理引用,最好使用C#的using块,以确保Lua的GC可以及时收集对象。) - 如果在由Lua调用的方法中返回对Lua对象的CLR对象引用,则在将控件返回给Lua之前,Eluant会为您处理引用。
1. 请参见此处。终结器存在,但如果由终结器调用,则清除程序不会释放Lua对象引用。换句话说,终结器基本上什么也不做。请注意,因为Lua不是线程安全的,所以实际上在此时释放Lua引用是不安全的,但是可以将释放操作排队等待后续处理。LuaInterface不会这样做;Eluant会这样做

1

简短回答

lua.doString("for key,value in ipairs(testTable) do testTable[key]=nil end");替换lua.newTable("testTable")即可。

这样就不会有内存泄漏,并且您的代码可以正常工作。

详细回答

假设您有这样的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实例的重置方式导致加载了多个版本的表。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接