C# 中的对象何时完全符合垃圾回收条件?
所以我知道这里的基础知识 - 当一个对象不再可以被根访问时(即来自堆栈帧中的局部变量或静态引用的强引用),该对象就有资格进行垃圾回收
我的问题是关于这种潜在的优化其中,即使一个对象是从局部变量引用的,它也可能在函数中不再引用该变量的任何点被垃圾收集。首先 - 现有的 C# 实现似乎并没有这样做 - 2.0 和 4.0 似乎都保持本地引用“活动”,直到堆栈帧被销毁。但是 - 如果在 CLR 的更高版本中优化垃圾收集,我还想编写仍然健壮的代码。
因此,言归正传,这里有一些代码说明:
class Foo
{
...
}
class Program
{
public static void fxn1(int blah)
{
...
}
public static void fxn2(Foo foo)
{
...
}
public static int ToInt(Foo foo)
{
...
}
public static void Main()
{
...
Foo foo = new Foo();
fxn2(foo); // I THINK foo may not be GC'ed until fxn2 returns...
// I THINK foo may be GC'ed here, even though CLR2.0 and CLR4.0 don't...
// (experiment shows CLR 2.0 and 4.0 leave foo "live" until Main returns)
fxn2(new Foo()); // I THINK the argument can't be GC'ed until fxn2 returns...
// I KNOW that even CLR2.0 and CLR4.0 will GC the argument after the return...
fxn1( ToInt(new Foo()) ); // I KNOW that new Foo is GC'able even within fxn1...
}
}
因此,最终,现有 CLR 的规则似乎是: 1. 任何对象在作为直接参数的函数调用期间都是“活动的” 2. 如果任何对象被未重新分配的本地堆栈变量引用,则该对象在函数调用期间处于“活动”状态。 (即使堆栈变量可能不会在函数末尾的多个指令中被引用)
但是 - 显然 C# 保留修改 (2) 的权利,以便对象在最终使用引用时一直处于“活动”状态功能。
这是否意味着:
Foo foo = new Foo();
Foo foo2 = new Foo();
fxn2(foo); // foo is NOT GC'able until fxn1 returns?
// foo IS GC'able from here on? (b/c no further uses of local "foo"?)
fxn2(foo2); // foo2 is NOT GC'able within fxn2 ?
fxn1(ToInt(foo2)); // foo2 IS GC'able within fxn1 ? (existing CLR does not GC foo2)
ECMA 规范中是否有详细处理垃圾收集资格的内容?
So I know the basics here - an object is eligible for garbage collection when it's no longer reachable by a root (i.e. a strong reference either from a local variable in a stack frame or a static reference)
The question I have is about this potential optimization where, even if an object is referenced from a local variable, it may be garbage collected at any point in a function where the variable is no longer referenced. First - it appears that existing implementations of C# don't do this - both 2.0 and 4.0 seem to keep local references "live" until the stack frame is destroyed. But - I'd also like to write code that is still robust if and when garbage collection is optimized in later versions of the CLR.
So - without further ado, here's some code illustration:
class Foo
{
...
}
class Program
{
public static void fxn1(int blah)
{
...
}
public static void fxn2(Foo foo)
{
...
}
public static int ToInt(Foo foo)
{
...
}
public static void Main()
{
...
Foo foo = new Foo();
fxn2(foo); // I THINK foo may not be GC'ed until fxn2 returns...
// I THINK foo may be GC'ed here, even though CLR2.0 and CLR4.0 don't...
// (experiment shows CLR 2.0 and 4.0 leave foo "live" until Main returns)
fxn2(new Foo()); // I THINK the argument can't be GC'ed until fxn2 returns...
// I KNOW that even CLR2.0 and CLR4.0 will GC the argument after the return...
fxn1( ToInt(new Foo()) ); // I KNOW that new Foo is GC'able even within fxn1...
}
}
So ultimately, the rules for existing CLR's seem to be:
1. any object is "live" for the duration of a function call for which it is an immediate argument
2. any object is "live" for the duration of a function call if it is referenced by a local stack variable that is not reassigned. (even if the stack variable may not be referenced for several instructions at the end of the function)
However - apparently C# reserves the right to modify (2) so that an object is "live" up until the final use of a reference within a function.
Would this mean:
Foo foo = new Foo();
Foo foo2 = new Foo();
fxn2(foo); // foo is NOT GC'able until fxn1 returns?
// foo IS GC'able from here on? (b/c no further uses of local "foo"?)
fxn2(foo2); // foo2 is NOT GC'able within fxn2 ?
fxn1(ToInt(foo2)); // foo2 IS GC'able within fxn1 ? (existing CLR does not GC foo2)
Is there anything in the ECMA spec which deals w/ garbage collection eligibility in detail?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
嗯,这里不可能给出一个通用的答案,因为什么时候事情真正符合 GC 的条件完全取决于运行时的实现。
您唯一可以信任的是保证 - 即,只要从堆栈引用一个对象,它就不会被收集。
但是,您无法从代码中判断何时从堆栈中删除局部变量 - 这很容易发生编译器优化 - 在静态编译器以及抖动中。
因此,无论现在是什么精确答案,在运行时的下一次小更新之后都可能不再是 - 通常最好编写不依赖于这些微妙之处的代码,这些微妙之处只能通过实验来发现,而是依赖于运行时的仅保证。
Well, it's impossible to give a general answer here, as when things actually become eligible for GC completely depends on your runtime's implementation.
The only thing you can trust are the guarantees - i.e., as long as an object is referenced from the stack, it won't be collected.
You cannot tell from the code when a local variable is removed from the stack, though - this is prone to compiler optimizations - in the static compiler as well as in the jitter.
So whatever may be a precise answer now may not be anymore after the next minor update of your runtime - it's usually best to write code that does not depend on such subtleties, which can only be found out by experiment, and instead relies on the runtime's guaranties only.
@M.Babcock - 感谢您提供 ECMA 规范的链接! 8.4 实际上太笼统了,但我正在寻找的答案是在 10.9 中 - 并且与 Java 相同 - 当一个变量不再被任何可能的未来代码路径引用时,那么它被认为有资格进行垃圾收集 - 这意味着尽管现有的 clr 实现似乎将局部变量生命周期限制在堆栈中,但不能保证第三方或未来的实现会这样做。
@M.Babcock - Thank you for the link to the ECMA spec! 8.4 was actually too general, but the answer I was looking for was in 10.9 - and is identical to Java - when a variable can be no longer referenced by any possible future code path, then it is considered eligible for garbage collection - which means that although the existing clr implementation seems to scope local variable lifetime to the stack, there's no guarantee that third party or future implementations will do so.
您提到了通过更快地收集对象来进行潜在优化的想法。
我不认为这是一种优化。
并不是说计算机拥有更多可用内存就运行得更快或更好。
分配要么成功,要么失败。
如果成功,则进程运行正常。
如果失败,则进程就会遇到麻烦。
(从可以恢复的小问题到导致进程终止的深度问题)
我只是不认为像您所描述的那样对 GC 采取更积极的态度有什么意义。它只会在边界情况下有帮助,即您已经分配了 99.99% 的内存来运行。在这种情况下,您将深入虚拟内存,并以疯狂的方式分页到磁盘。
You refer to the idea of a potential optimization by collecting an object sooner.
I don't see this as an optimization.
Its not as if the computer runs faster or better if it has more free memory.
Either an allocation succeeds or fails.
If it succeeds, the process is running fine.
If it fails, the process is in trouble.
(anything from trivial-trouble, which it can recover from, to deep-trouble which results in process termination)
I just don't see the point in being more aggressive about G.C. as you describe. It would only help in the boundary case where you're already running with 99.99% of memory allocated. And in that case, you're deep into virtual memory, and paging to disk in a crazy fashion.