c# - 使用带有终结器的 C++/CLI 定义类时 C# 中的内存泄漏

标签 c# memory-leaks garbage-collection c++-cli finalizer

当我在 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 中实现终结器时都会发生这种内存泄漏(或垃圾收集的不良工作)。

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


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

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

最佳答案

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

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

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

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

因此,您的 OOM 是意料之中的。

关于c# - 使用带有终结器的 C++/CLI 定义类时 C# 中的内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15521233/

相关文章:

C# 不接受 c1、c2、c3 作为定义的变量

java - 一段时间后重置变量

c++ - 在内存泄漏等方面还可以吗

java - Java 中何时以及如何收集类垃圾?

java - 错误java.lang.OutOfMemoryError:超出了GC开销限制

C# 手动排序列表中的父/子树元素

c# - 值类型、引用类型和装箱

c# - 获取文件的相对路径 C#

android - 回收位图不释放内存

c++ - 确定 c/c++ 代码中内存泄漏的工具