繁体   English   中英

在 C# 或 VB.Net 中创建 memory 泄漏

[英]Creating a memory leak in C# or VB.Net

受到这个问题的启发,想知道在.Net 中创建 memory 泄漏的可能方法是什么。 我曾经发现一个带有 ODBC 数据访问的。 有没有人有最新版本的经验?

编辑:是否有任何看似正常、无害的用途会导致 memory 泄漏?

创建 memory 泄漏的最简单方法是滥用为互操作设计的设施,因为它们处理非托管 memory。

例如,分配一个指向GCHandle的 GCHandle,并且永远不要释放它。

编辑:是否有任何看似正常、无害的用途会导致 memory 泄漏?

我只知道一个,尤其是在一些 UI 程序中。 这在 WinForms 中是可能的,但直到最近才因为 WPF 和 MVVM 而变得普遍:

许多人忽略的关键点是委托包含对其运行的 object 的引用。

因此,如果使用具有双向绑定(使用事件实现,由委托组成)的模式,例如 MVVM,并且如果视图更改为具有相同 ViewModel 的不同视图,则默认情况下,ViewModel 的更新仍然绑定到两者视图,这会导致旧视图泄漏。

理论上,任何委托都可能发生这种情况,但实际上并不常见,因为订阅者通常比发布者更长寿或取消订阅。

取消订阅 lambda 事件处理程序时也存在类似情况:

timer.Elapsed += (_, __) => myObj.Notify();
timer.Elapsed -= (_, __) => myObj.Notify();

在这种情况下,即使 lambda 表达式相同,它们代表不同的处理程序,因此Elapsed事件仍将调用Notify 上面的第二行没有效果; 它不会取消订阅,也不会引发错误。

请注意,不正确的取消订阅通常会在测试期间被发现,因此它们很少会导致已发布代码中的“泄漏”。 相比之下,上述 MVVM 情况不会导致可观察到的副作用(除了 memory 和资源泄漏)。

GCHandle.Alloc()是在 .NET 中创建“真正的”memory 泄漏的绝妙方法。 (“真正的”泄漏,因为 完全无法 到达,没有黑客/反射就无法到达,但仍然泄漏)

编辑

编辑:是否有任何看似正常、无害的用途会导致 memory 泄漏?

“看似正常”取决于一个人的知识/经验。

例如System.Windows.Forms.Timer在启用时“扎根”自身(实际上是通过GCHandle.Alloc() )。 如果您通过 Visual Studio 的图形编辑器将Timer添加到Form中,VS 将

  • 将“组件”集合添加到您的 class
  • 生成将Timer添加到该“组件”集合的代码
  • 生成代码,在 Form 的Dispose()方法中处理“组件”集合中的所有内容

这意味着事情将按预期工作,没有泄漏。

但是,如果您自己添加创建和启动Timer的代码,很容易忘记添加停止/处理它的代码。 而且 Visual Studio 不会(不能)为你做这件事。

在这种情况下, Timer将保持活动状态。 永远不会被收集。 它将继续运行(并触发事件)。 即使Form已关闭/处置。

而且,由于您通常会将TimerTick事件连接到Form的某些成员 function ,因此Form也将保持活动状态。 Timer有根, Timer引用事件委托,事件委托引用Form 。)

由于仍然有很多人不知道或不关心这样的东西,所以代码对他们来说看起来很“正常”。

如果您通读链接中提供的所有答案,它们几乎都适用于.Net。 虽然 CLR 和 JVM 是完全不同的系统,但它们的设计理念仍然非常相似(具体来说,它们都是托管系统),因此它们具有许多相同的优势和缺陷。

终结器滥用会导致“内存泄漏”,即这将创建一个 object,其 memory 永远不会被 GC 声明:

public class Foo
{
    int[] value = new int[100];

    ~Foo()
    {
        GC.ReRegisterForFinalize(this);
    }
}

要利用的一种模式是 class 有一个私有集合,没有人从中删除,但有人不断添加对象。

假设有一个后台线程应该处理来自 static 阻塞集合的元素并且线程死亡或阻塞。 然后集合只能增长,导致泄漏:

public class Test
{
    static Test()
    {
        Task.Factory.StartNew(() =>
        {
            Random r = new Random();

            try
            {
                while (true)
                {
                    object o = col.Take();

                    //process o fails at some point
                    if (r.Next(100) == 0)
                    {
                        Console.WriteLine("Fail! No one is processing anymore.");
                        throw new Exception();
                    }
                }
            }
            catch
            {
                Console.WriteLine("We caught the exception, but didn't resume processing");
            }
        });
    }

    private static BlockingCollection<object> col = new BlockingCollection<object>();

    public void Add(object o)
    {
        col.Add(o);
    }
}

class Program {
    public static void Main(string[] args)
    {
        Test t = new Test();
        while (true)
            t.Add(new object());
    }
}
for (;;)
    Marshal.AllocHGlobal(0x400);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM