谓词不释放内存的地方
Where predicates does not releases memory
如果我在 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 = null
或 predicate = null
,它也不会释放内存。
我已将 List<object> 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
中的引用。
嗯...有趣...甚至 Expression<>
导致关闭...我不知道...
最终结果:谓词没有对 myList
的引用
我来解释一下:
private static bool IsDebug()
{
// Taken from
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) 的情况下在发布模式下执行此代码非常重要。如果你不这样做,你会在程序启动时得到一个警告
前两个 "parts" 只是为了表明 List<>
确实保留了 "alive" 它引用的项目(因此在这种情况下是 byte[]
),并释放List<>
或 .Clear()
它让 GC 收集 byte[]
.
第三部分更有趣:有一个List<>
和一个Expression<>
...两者似乎都保留了对byte[]
的引用,但这是一种错觉.所写的 Expression<>
会导致编译器在 myList<>
变量周围生成一个 "closure"。使用 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生成的代码以获得更简单的示例)
编译器生成了一个隐藏的class<>c__DisplayClassb
,字段为myList
。因此,该方法没有 "local" 变量 myList
,而是具有字段 myList
的局部变量 <>c__DisplayClassb
。 predicate1
不直接保留对 myList
的引用,而是对变量 <>c__DisplayClassb
的引用(请参阅 Expression.Constant(<>c__DisplayClassb)
?),所以当
<>c__DisplayClassb.myList = null;
predicate1
确实仍然引用了 <>c__DisplayClassb
,但是 <>c__DisplayClassb.myList
是 null
,因此不再有对 myList
的引用。
如果我在 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 = null
或 predicate = null
,它也不会释放内存。
我已将 List<object> 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
中的引用。
嗯...有趣...甚至 Expression<>
导致关闭...我不知道...
最终结果:谓词没有对 myList
的引用我来解释一下:
private static bool IsDebug()
{
// Taken from
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) 的情况下在发布模式下执行此代码非常重要。如果你不这样做,你会在程序启动时得到一个警告
前两个 "parts" 只是为了表明 List<>
确实保留了 "alive" 它引用的项目(因此在这种情况下是 byte[]
),并释放List<>
或 .Clear()
它让 GC 收集 byte[]
.
第三部分更有趣:有一个List<>
和一个Expression<>
...两者似乎都保留了对byte[]
的引用,但这是一种错觉.所写的 Expression<>
会导致编译器在 myList<>
变量周围生成一个 "closure"。使用 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生成的代码以获得更简单的示例)
编译器生成了一个隐藏的class<>c__DisplayClassb
,字段为myList
。因此,该方法没有 "local" 变量 myList
,而是具有字段 myList
的局部变量 <>c__DisplayClassb
。 predicate1
不直接保留对 myList
的引用,而是对变量 <>c__DisplayClassb
的引用(请参阅 Expression.Constant(<>c__DisplayClassb)
?),所以当
<>c__DisplayClassb.myList = null;
predicate1
确实仍然引用了 <>c__DisplayClassb
,但是 <>c__DisplayClassb.myList
是 null
,因此不再有对 myList
的引用。