4

当我在 C++/CLI DLL 中实现一个类时:

public ref class DummyClass
{
protected:
    !DummyClass() 
    {
        // some dummy code:
        std::cout << "hello" << std::endl;
    }
}

当我将该 DLL 加载到 C# 项目并通过重复创建对象来使用该类时:

static void Main()
{
    while (true)
    {
        var obj = new DummyClass();
    }
}

然后,在运行程序时,内存会慢慢消化到 OutOfMemoryException。

看来,每次我在 C++/CLI 中实现终结器时,都会发生这种内存泄漏(或垃圾收集的不良工作)。

为什么会发生这种内存泄漏?我怎样才能避免它并且仍然能够将终结器用于其他(更复杂的)用途?


更新:原因肯定不是在终结器中写入 Console / stdout 或其他非标准代码,以下类具有相同的内存泄漏行为:

public ref class DummyClass
{
private:
    double * ptr;
public:
    DummyClass()
    {
         ptr = new double[5];
    }
protected:
    !DummyClass() 
    {
         delete [] ptr;
    }
}
4

2 回答 2

4

当您分配的速度超过垃圾收集的速度时,您将遇到 OOM。如果您进行大量分配,CLR 将插入 Sleep(xx) 来限制分配,但在极端情况下这还不够。

当你实现一个终结器时,你的对象被添加到终结队列中,当它被终结时,它被从队列中删除。这确实会带来额外的开销,并且您会使对象的寿命比必要的更长。即使您的对象可以在廉价的 Gen 0 GC 期间被释放,它仍然会被终结队列引用。当发生完整的 GC 时,CLR 会触发 finalizaion 线程开始清理。这无济于事,因为您的分配速度确实比您可以完成的速度快(写入标准输出非常慢),并且您的完成队列将变得越来越大,导致完成时间越来越慢。

我没有测量它,但我认为即使是一个空的终结器也会导致这个问题,因为增加的对象生命周期和两个终结队列处理(终结器队列和 f-reachable 队列)确实施加了足够的开销,使终结器比分配慢。

您需要记住,终结是一种固有的异步操作,在特定时间点没有执行保证。在允许额外分配之前,CLR 永远不会等待清除所有挂起的终结器。如果你在 10 个线程上分配,那么在你之后仍然会有一个终结器线程清理。如果您想依赖确定性终结,则需要通过调用 GC.WaitForPendingFinalizers() 来等待,但这会使您的性能陷入停顿。

因此,您的 OOM 是预期的。

于 2013-03-20T12:02:26.937 回答
0

您应该使用 AddMemoryPressure 函数,否则垃圾收集器会低估及时清理这些对象的需要。

于 2013-03-21T07:14:09.250 回答