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

luinterface内存泄漏问题

我需要抱怨一下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块为你做处置)。

为了澄清,释放CLR 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实例正在加载表的多个版本,因为它被重置的方式。