C#为什么我的示例代码中存在内存泄漏?

时间:2017-09-09 04:38:35

标签: c# .net memory-leaks

public class Program
{
    public static void Main(string[] args)
    {
        WeakReference wfRaiser;
        WeakReference wfSubscriber;

        var before = GC.GetTotalMemory(true);

        //region 1: scope of raiser
        {
            var raiser = new Raiser();
            wfRaiser = new WeakReference(raiser);
            //region 2: scope of subscriber
            {
                var subscriber = new Subscriber();
                wfSubscriber = new WeakReference(subscriber);
                raiser.ValueChanged += subscriber.HandlingMethod;
            }

            raiser.SetValue(153);
        }

        Thread.Sleep(2000);
        GC.Collect();
        Thread.Sleep(2000);

        var after = GC.GetTotalMemory(true);

        Console.WriteLine($"Is raiser alive? {wfRaiser.IsAlive}\n" +
                          $"Is subscriber alive? {wfSubscriber.IsAlive}\n" +
                          "Memory size (bytes):\n" +
                          $"- before : {before} \n" +
                          $"- after : {after}\n" +
                          $"=> Leak: {after - before}");
    }
}

public class Raiser
{
    public event EventHandler<int> ValueChanged;
    public void SetValue(int value) => ValueChanged?.Invoke(this, value);
}

public class Subscriber
{
    private readonly double[] MyData = new double[9999];
    public void HandlingMethod(object sender, int value) => Console.WriteLine($"Value changed: {value}");
}

我不明白此代码中内存泄漏的原因。 我知道提升者仍然引用订阅者(通过事件处理程序),即使是在区域2之外。但是,我认为它们都应该在区域1之后释放 raiser

正如弱参考文献所示,提升者和订阅者都被释放。那为什么还有内存泄漏?

我在Release模式下构建代码并启用了代码优化。 这是我的输出:

Value changed: 153
Is raiser alive? False
Is subscriber alive? False
Memory size (bytes):
- before : 21960
- after : 29520
=> Leak: 7560

1 个答案:

答案 0 :(得分:1)

您的值只是在后台创建的其他内容的固定成本开销,如果您在程序中运行两次测试,则第二次和任何其他后续运行将更改0字节。

在代码上运行内存探查器,发现7590的大部分内容是string的内部char[]TextWriter缓冲区Console.WriteLine()内部使用。

以下是您的测试的调整版本,在我的机器上进行第二次测试时返回0。

using System;
using System.Threading;

public class Program
{
    public static void Main(string[] args)
    {
        RunTest();
        RunTest();
    }

    private static void RunTest()
    {
        //WeakReference wfRaiser;
        //WeakReference wfSubscriber;

        var before = GC.GetTotalMemory(true);

        //region 1: scope of raiser
        {
            var raiser = new Raiser();
            //wfRaiser = new WeakReference(raiser);
            //region 2: scope of subscriber
            {
                var subscriber = new Subscriber();
                //wfSubscriber = new WeakReference(subscriber);
                raiser.ValueChanged += subscriber.HandlingMethod;
            }

            raiser.SetValue(153);
        }

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        var after = GC.GetTotalMemory(true);

        Console.WriteLine(//$"Is raiser alive? {wfRaiser.IsAlive}\n" +
                          //$"Is subscriber alive? {wfSubscriber.IsAlive}\n" +
                          "Memory size (bytes):\n" +
                          $"- before : {before} \n" +
                          $"- after : {after}\n" +
                          $"=> Leak: {after - before}");
    }
}

public class Raiser
{
    public event EventHandler<int> ValueChanged;
    public void SetValue(int value) => ValueChanged?.Invoke(this, value);
}

public class Subscriber
{
    private readonly double[] MyData = new double[9999];
    public void HandlingMethod(object sender, int value) => Console.WriteLine($"Value changed: {value}");
}