2016-11-07 173 views
2

我发现我在C#/ Lua LuaInterface项目中有一个错误的内存泄漏。我已经在C#中写了一个简单的测试函数,每隔0.5秒从Lua中调用一次。我可以看到每个循环都会增加Lua内存使用量。我最新的C#端代码LuaInterface内存泄漏问题

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 

任何帮助解决我的内存泄漏问题将不胜感激。

再次感谢

杰夫

回答

1

我要咆哮约LuaInterface一点,因为它使这个问题很难解决 - 在某些情况下,它甚至不可能不泄漏内存。

LuaInterface(和NLua)中的CLR对象代理不提供释放Lua引用的终结器。 您必须处理引用Lua对象的每个CLR对象。如果你忘记一次,Lua对象永远不会被垃圾收集。

更糟的是,您无法正确处理从Lua调用的CLR方法返回的引用。如果您在退回参考文献之前丢弃该参考文件,则该参考文件不存在,您无法再将其退回。如果您不处理它,则CLR参考被泄漏。有代码体操,你可以做适当的处理参考,但没有任何理由应该需要你的这种工作。

现在让我从我的肥皂箱下载并解决您的代码。

在泄漏这些对象的所有方法中有很多地方。您对GetTable()的使用掩盖了您对其工作原理的误解。每次调用此方法时,都会得到一个新的 CLR对象,该对象引用由该Lua全局变量引用的Lua表。您的使用似乎假设您可以执行m_lua.GetTable("tabx").Dispose()并完成某些操作 - 所有这些操作都会创建一个新的CLR参考,然后仅处理一个参考即可处理。换句话说,这是一种无所作为的昂贵方式。

GetTable()每次调用应该有一个相应的处置(或使用C#的using块做处理你)。

澄清,配置CLR LuaTable对象不会摧毁Lua表!它所做的就是发布那特别是 CLR引用表。

这适用于 LuaInterface类型引用一个Lua对象,包括函数,这是另一个可能泄漏的地方。

在这里,我将展示每种方法的问题,以及如何重写它以解决这些问题。

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]; 
    } 
} 

要获得回到我的soapbox上,考虑使用Eluant来代替,它是CLR的一组Lua绑定(非常像LuaInterface),只是它解决了内存管​​理问题。 (免责声明:我洗脱液的作者)

特别是,它解决了你遇到了这里的问题:

  • 洗脱液的CLR对象引用的Lua对象有终结,将排队的Lua参考稍后发布。如果你忘记处理一个引用Lua对象的CLR对象,它最终会被收集起来。 (但你仍然应该尽快处理引用,最好使用C#的using块,以确保Lua的GC可以及时收集对象。)
  • 如果您返回一个CLR对象引用到一个方法中的Lua对象由Lua调用,Eluant会在您将控制权返还给Lua之前为您处置参考。

See here。终结器存在,但如果终结器调用了处理器,它不会释放Lua对象引用。换句话说,终结者基本上什么都不做。请注意,由于Lua不是线程安全的,因此在此时实际释放Lua引用并不安全,但释放操作可能会在稍后排队。 Lua接口不这样做; Eluant does

1

简短的回答

更换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的实例加载的,因为它被重置的方式表的多个版本。