【问题标题】:Where predicates does not releases memory谓词不释放内存的地方
【发布时间】:2015-07-18 21:47:44
【问题描述】:

如果我在 where predicate 中包含外部引用,则内存不会得到释放。

假设我有一个List<object>,那么如果我像这样写一个where predicate

    List<object> myList = new List<object>();
    ...
    myList.add(object);
    ...

    Expression<Func<object,bool>> predicate = p => myList.Contains(p);

即使我创建myList = nullpredicate = null,它也不会释放内存。

我将List&lt;object&gt; itemsource 绑定到DataGrid。我也把它的ItemSource设为null,把DataGrid、DataGrid设为null。 .我还用 ANTS Memory Profiler 7.4 分析了这个问题。它还告诉我,因为 wherepredicate 它持有引用。

如果我像这样在dispose() 中更改我的wherepredicate,那么内存就会被释放。

    Expression<Func<object,bool>> predicate = p => p.id == 0;

这意味着删除 WherePredicate 中的引用。

【问题讨论】:

  • 请展示一个简短但完整的程序来说明问题。我怀疑它可能是您使用的分析器有问题,或者您可能误解了它。
  • (如果您不打算回应澄清请求,那么悬赏问题没有多大意义......)
  • 嘿,约翰……我已经解释了我在项目中的使用方式……我已经用 Ants 内存分析器分析了这个问题……但它显示了匿名类型……
  • 所以等到您不在移动设备上,然后生成一个简短但完整的程序,我们可以使用它来尝试重现该问题。 (我在 4 天前问过这个问题——我怀疑你一直被困在移动设备上......)
  • 你不是在问问题。是的,使用对象的表达式会保留对该对象的引用,并且只要表达式还活着,它就会一直保持活跃。这不是一个问题。如果您希望对象引用的对象能够被清理,那么引用它们的对象需要符合收集条件。

标签: c# memory datagrid


【解决方案1】:

嗯...有趣...甚至Expression&lt;&gt;导致关闭...我不知道...

最终结果:谓词没有对 myList 的引用

我会解释:

private static bool IsDebug()
{
    // Taken from http://stackoverflow.com/questions/2104099/c-sharp-if-then-directives-for-debug-vs-release
    object[] customAttributes = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(DebuggableAttribute), false);

    if ((customAttributes != null) && (customAttributes.Length == 1))
    {
        DebuggableAttribute attribute = customAttributes[0] as DebuggableAttribute;
        return (attribute.IsJITOptimizerDisabled && attribute.IsJITTrackingEnabled);
    }

    return false;
}

static void Main(string[] args)
{
    // Check x86 or x64
    Console.WriteLine(IntPtr.Size == 4 ? "x86" : "x64");

    // Check Debug/Release
    Console.WriteLine(IsDebug() ? "Debug, USELESS BENCHMARK" : "Release");

    // Check if debugger is attached
    Console.WriteLine(System.Diagnostics.Debugger.IsAttached ? "Debugger attached, USELESS BENCHMARK!" : "Debugger not attached");

    Console.WriteLine();

    {
        long memory = GC.GetTotalMemory(true);

        // A big array, big enough that we can see its allocation in
        // memory
        byte[] buffer = new byte[10000000];

        Console.WriteLine("Just allocated the array: {0}", GC.GetTotalMemory(true) - memory);

        // A List<>, containing a reference to the buffer
        List<object> myList = new List<object>();
        myList.Add(buffer);

        Console.WriteLine("Added to the List<>: {0}", GC.GetTotalMemory(true) - memory);

        // We want to be sure that buffer is referenced at least up to
        // this point
        GC.KeepAlive(buffer);

        // But clearly setting buffer = null is useless, because the
        // List<> has anothe reference
        buffer = null;
        Console.WriteLine("buffer = null: {0}", GC.GetTotalMemory(true) - memory);

        // If I Clear() the List<>, the last reference to the buffer
        // is removed, and now the buffer can be freed
        myList.Clear();
        Console.WriteLine("myList.Clear(): {0}", GC.GetTotalMemory(true) - memory);

        GC.KeepAlive(myList);
    }

    Console.WriteLine();
    GC.Collect();

    {
        long memory = GC.GetTotalMemory(true);

        // A big array, big enough that we can see its allocation in
        // memory
        byte[] buffer = new byte[10000000];

        Console.WriteLine("Just allocated the array: {0}", GC.GetTotalMemory(true) - memory);

        // A List<>, containing a reference to the buffer
        List<object> myList = new List<object>();
        myList.Add(buffer);

        Console.WriteLine("Added to the List<>: {0}", GC.GetTotalMemory(true) - memory);

        // We want to be sure that buffer is referenced at least up to
        // this point
        GC.KeepAlive(buffer);

        // But clearly setting buffer = null is useless, because the
        // List<> has another reference
        buffer = null;
        Console.WriteLine("buffer = null: {0}", GC.GetTotalMemory(true) - memory);

        // We want to be sure that the List<> is referenced at least
        // up to this point
        GC.KeepAlive(myList);

        // If I set to null myList, the last reference to myList
        // and to buffer are removed
        myList = null;
        Console.WriteLine("myList = null: {0}", GC.GetTotalMemory(true) - memory);
    }

    Console.WriteLine();
    GC.Collect();

    {
        long memory = GC.GetTotalMemory(true);

        // A big array, big enough that we can see its allocation in
        // memory
        byte[] buffer = new byte[10000000];

        Console.WriteLine("Just allocated the array: {0}", GC.GetTotalMemory(true) - memory);

        // A List<>, containing a reference to the buffer
        List<object> myList = new List<object>();
        myList.Add(buffer);

        Console.WriteLine("Added to the List<>: {0}", GC.GetTotalMemory(true) - memory);

        // A predicate, containing a reference to myList
        Expression<Func<object, bool>> predicate1 = p => myList.Contains(p);
        Console.WriteLine("Created a predicate p => myList.Contains(p): {0}", GC.GetTotalMemory(true) - memory);

        // A second predicate, **not** containing a reference to
        // myList
        Expression<Func<object, bool>> predicate2 = p => p.GetHashCode() == 0;
        Console.WriteLine("Created a predicate p => p.GetHashCode() == 0: {0}", GC.GetTotalMemory(true) - memory);

        // We want to be sure that buffer is referenced at least up to
        // this point
        GC.KeepAlive(buffer);

        // But clearly setting buffer = null is useless, because the
        // List<> has another reference
        buffer = null;
        Console.WriteLine("buffer = null: {0}", GC.GetTotalMemory(true) - memory);

        // We want to be sure that the List<> is referenced at least
        // up to this point
        GC.KeepAlive(myList);

        // If I set to null myList, an interesting thing happens: the
        // memory is freed, even if the predicate1 is still alive!
        myList = null;
        Console.WriteLine("myList = null: {0}", GC.GetTotalMemory(true) - memory);

        // We want to be sure that the predicates are referenced at 
        // least up to this point
        GC.KeepAlive(predicate1);
        GC.KeepAlive(predicate2);

        try
        {
            // We compile the predicate1
            Func<object, bool> fn = predicate1.Compile();
            // And execute it!
            fn(5);
        }
        catch (NullReferenceException)
        {
            Console.WriteLine("predicate1 is 'pointing' to a null myList");
        }
    }
}

这是一个分三个部分的示例测试:基本点是分配了一个大的byte[] 数组,通过检查分配了多少内存,我们检查该数组是否仍然以某种方式分配。 在没有调试器 (CTRL+F5) 的情况下在 Release 模式下执行此代码非常重要。如果不这样做,程序启动时会收到警告

前两个“部分”仅表明 List&lt;&gt; 确实保持“活动”它引用的项目(因此在这种情况下为 byte[]),并释放 List&lt;&gt;.Clear()ing 它让 GC 收集byte[]

第三部分更有趣:有List&lt;&gt;Expression&lt;&gt;...两者似乎都保留了对byte[] 的引用,但这是一种错觉。所写的Expression&lt;&gt; 导致编译器围绕myList&lt;&gt; 变量生成一个“闭包”。使用 ILSpy 很容易看到:

Program.<>c__DisplayClassb <>c__DisplayClassb = new Program.<>c__DisplayClassb();
<>c__DisplayClassb.myList = new List<object>();
<>c__DisplayClassb.myList.Add(buffer3);

ParameterExpression parameterExpression = Expression.Parameter(typeof(object), "p");
Expression<Func<object, bool>> predicate = Expression.Lambda<Func<object, bool>>(Expression.Call(Expression.Field(Expression.Constant(<>c__DisplayClassb), fieldof(Program.<>c__DisplayClassb.myList)), methodof(List<object>.Contains(!0)), new Expression[]
{
    parameterExpression
}), new ParameterExpression[]
{
    parameterExpression
});

(如果你没有ILSpy,可以看一下在线编译器TryRoslyn生成的代码更简单的示例)

编译器生成一个隐藏类&lt;&gt;c__DisplayClassb,带有一个字段myList。因此,该方法没有“局部”变量myList,而是将其作为具有字段myList 的局部变量&lt;&gt;c__DisplayClassbpredicate1 不直接保留对myList 的引用,而是对变量&lt;&gt;c__DisplayClassb 的引用(参见Expression.Constant(&lt;&gt;c__DisplayClassb)?),所以当

<>c__DisplayClassb.myList = null;

predicate1 仍然有对&lt;&gt;c__DisplayClassb 的引用,但&lt;&gt;c__DisplayClassb.myListnull,所以没有更多对myList 的引用。

【讨论】:

  • @AmolBavannavar 您可能对谓词、列表(您说您有一个DataGrid)、项目等有多个引用,并且您显示的代码可能不是你正在运行的代码。
  • @AmolBavannavar 写了一个更大的样本。
  • @xanatos 是的,这就是我想知道的......非常感谢......这是一个规范的答案......
  • 是的..通过清除列表,它释放了内存...您之前的答案是绝对正确的..但只是想知道为什么会发生这种情况?...任何方式..非常感谢。 ..
猜你喜欢
  • 2018-07-30
  • 2017-06-29
  • 1970-01-01
  • 1970-01-01
  • 2011-06-30
  • 2016-09-14
  • 2015-08-19
  • 2012-05-15
  • 2018-01-26
相关资源
最近更新 更多