垃圾收集器在收集可达对象的实例属性或字段时的行为不明确
Unclear behavior by Garbage Collector while collecting instance properties or fields of reachable object
直到今天我还在想可达对象的成员也被认为是可达的。
但是,今天我发现了一种行为,当 Optimize Code
被选中时 或应用程序正在在释放模式中执行。很明显,发布模式也归结为代码优化。所以,代码优化似乎是造成这种行为的原因。
让我们看一下代码:
public class Demo
{
public Action myDelWithMethod = null;
public Demo()
{
myDelWithMethod = new Action(Method);
// ... Pass it to unmanaged library, which will save that delegate and execute during some lifetime
// Check whether object is alive or not after GC
var reference = new WeakReference(myDelWithMethod, false);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
Console.WriteLine(reference.IsAlive);
// end - Check whether object is alive or not after GC
}
private void Method() { }
}
我稍微简化了代码。实际上,我们使用的是我们的特殊代表,而不是 Action
。但是行为是一样的。这段代码是用"members of reachable objects are also considered to be reachable"写的。但是,该委托将由 GC 尽快收集。我们必须将它传递给一些非托管库,这些库将使用它一段时间。
您只需将该行添加到 Main
方法即可测试演示:
var p = new Demo();
我能理解优化的原因,但是在不创建另一个函数的情况下防止这种情况的推荐方法是什么,该函数将使用将从某个地方调用的变量myDelWithMethod
? 一,我发现的选项,如果我在构造函数中设置 myDelWithMethod
就可以了:
myDelWithMethod = () => { };
然后,直到拥有的实例被收集,它才会被收集。如果将 lambda 表达式设置为一个值,它似乎无法以相同的方式优化代码。
所以,很高兴听到您的想法。这是我的问题:
是不是可达对象的成员也被认为是
是否可达?
为什么lambda表达式不收集?
在这种情况下有什么推荐的方法来防止收集?
无论这听起来多么奇怪,JIT 能够将对象视为不可访问,即使对象的实例方法正在执行 - 包括构造函数。
一个例子是下面的代码:
static void Main(string[] args)
{
SomeClass sc = new SomeClass() { Field = new Random().Next() };
sc.DoSomethingElse();
}
class SomeClass
{
public int Field;
public void DoSomethingElse()
{
Console.WriteLine(this.Field.ToString());
// LINE 2: further code, possibly triggering GC
Console.WriteLine("Am I dead?");
}
~SomeClass()
{
Console.WriteLine("Killing...");
}
}
可能打印:
615323
Killing...
Am I dead?
这是因为 内联 和 Eager Root 收集技术 - DoSomethingElse
方法不使用任何 SomeClass
字段,因此在 LINE 2
.
之后不再需要 SomeClass
实例
这恰好发生在您的构造函数中的代码中。在 // ... Pass it to unmanaged library
行之后,您的 Demo
实例变得不可访问,因此它的字段 myDelWithMethod
。这回答了第一个问题。
空 lamba 表达式的情况不同,因为在这种情况下此 lambda 缓存在静态字段中,始终可访问:
public class Demo
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static Action <>9__1_0;
internal void <.ctor>b__1_0()
{
}
}
public Action myDelWithMethod;
public Demo()
{
myDelWithMethod = (<>c.<>9__1_0 ?? (<>c.<>9__1_0 = new Action(<>c.<>9.<.ctor>b__1_0)));
}
}
关于此类场景中的推荐方式,您需要确保 Demo
的生命周期足够长以涵盖所有非托管代码执行。这实际上取决于您的代码架构。您可以使 Demo
静态,或在与非托管代码范围相关的受控范围内使用它。这真的取决于。
直到今天我还在想可达对象的成员也被认为是可达的。
但是,今天我发现了一种行为,当 Optimize Code
被选中时 或应用程序正在在释放模式中执行。很明显,发布模式也归结为代码优化。所以,代码优化似乎是造成这种行为的原因。
让我们看一下代码:
public class Demo
{
public Action myDelWithMethod = null;
public Demo()
{
myDelWithMethod = new Action(Method);
// ... Pass it to unmanaged library, which will save that delegate and execute during some lifetime
// Check whether object is alive or not after GC
var reference = new WeakReference(myDelWithMethod, false);
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
GC.WaitForPendingFinalizers();
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced, true);
Console.WriteLine(reference.IsAlive);
// end - Check whether object is alive or not after GC
}
private void Method() { }
}
我稍微简化了代码。实际上,我们使用的是我们的特殊代表,而不是 Action
。但是行为是一样的。这段代码是用"members of reachable objects are also considered to be reachable"写的。但是,该委托将由 GC 尽快收集。我们必须将它传递给一些非托管库,这些库将使用它一段时间。
您只需将该行添加到 Main
方法即可测试演示:
var p = new Demo();
我能理解优化的原因,但是在不创建另一个函数的情况下防止这种情况的推荐方法是什么,该函数将使用将从某个地方调用的变量myDelWithMethod
? 一,我发现的选项,如果我在构造函数中设置 myDelWithMethod
就可以了:
myDelWithMethod = () => { };
然后,直到拥有的实例被收集,它才会被收集。如果将 lambda 表达式设置为一个值,它似乎无法以相同的方式优化代码。
所以,很高兴听到您的想法。这是我的问题:
是不是可达对象的成员也被认为是 是否可达?
为什么lambda表达式不收集?
在这种情况下有什么推荐的方法来防止收集?
无论这听起来多么奇怪,JIT 能够将对象视为不可访问,即使对象的实例方法正在执行 - 包括构造函数。
一个例子是下面的代码:
static void Main(string[] args)
{
SomeClass sc = new SomeClass() { Field = new Random().Next() };
sc.DoSomethingElse();
}
class SomeClass
{
public int Field;
public void DoSomethingElse()
{
Console.WriteLine(this.Field.ToString());
// LINE 2: further code, possibly triggering GC
Console.WriteLine("Am I dead?");
}
~SomeClass()
{
Console.WriteLine("Killing...");
}
}
可能打印:
615323
Killing...
Am I dead?
这是因为 内联 和 Eager Root 收集技术 - DoSomethingElse
方法不使用任何 SomeClass
字段,因此在 LINE 2
.
SomeClass
实例
这恰好发生在您的构造函数中的代码中。在 // ... Pass it to unmanaged library
行之后,您的 Demo
实例变得不可访问,因此它的字段 myDelWithMethod
。这回答了第一个问题。
空 lamba 表达式的情况不同,因为在这种情况下此 lambda 缓存在静态字段中,始终可访问:
public class Demo
{
[Serializable]
[CompilerGenerated]
private sealed class <>c
{
public static readonly <>c <>9 = new <>c();
public static Action <>9__1_0;
internal void <.ctor>b__1_0()
{
}
}
public Action myDelWithMethod;
public Demo()
{
myDelWithMethod = (<>c.<>9__1_0 ?? (<>c.<>9__1_0 = new Action(<>c.<>9.<.ctor>b__1_0)));
}
}
关于此类场景中的推荐方式,您需要确保 Demo
的生命周期足够长以涵盖所有非托管代码执行。这实际上取决于您的代码架构。您可以使 Demo
静态,或在与非托管代码范围相关的受控范围内使用它。这真的取决于。