垃圾收集器如何在这里避免无限循环?


101

考虑下面的C#程序,我在codegolf上提交了它作为创建不循环的循环的答案:

class P{
    static int x=0;
    ~P(){
        System.Console.WriteLine(++x);
        new P();
    }
    static void Main(){
        new P();
    }
}

在我的检查中,该程序看起来像一个无限循环,但它似乎运行了数千次迭代,然后该程序成功终止且没有错误(没有引发任何错误)。P最终调用finalizer是否违反规范?

显然,这是愚蠢的代码,永远都不会出现,但是我对程序如何完成感到好奇。

原始代码高尔夫球场文章:: /codegolf/33196/loop-without-looping/33218#33218


49
恐怕要运行这个。
Eric Sc​​herrer 2014年

6
终结器不被调用肯定在有效行为的范围之内。我不知道为什么要花几千次迭代才麻烦,我希望零调用。

27
CLR具有防止终结器线程永远无法完成其工作的保护。2秒后将其强行终止。
Hans Passant 2014年

2
因此,标题中您问题的真正答案是,它可以通过使无限循环运行40秒然后终止来避免它。
Lasse V. Karlsen 2014年

4
通过尝试,似乎该程序会在2秒后杀死所有内容,无论如何。实际上,如果您继续产生线程,它将持续更长的时间:)
Michael B

Answers:


110

根据第二版CLR中的Richter(通过C#)(是的,我需要更新):

第478页

对于(CLR关闭),每个Finalize方法大约需要两秒钟的时间才能返回。如果Finalize方法在两秒钟内未返回,则CLR只是终止进程-不再调用Finalize方法。同样,如果调用所有对象的Finalize方法花费的时间超过40秒,那么CLR只会终止该过程。

而且,正如Servy提到的那样,它具有自己的线程。


5
此代码中的每个finalize方法每个对象耗时不到40秒。创建一个新对象然后有资格终结的问题与当前终结器无关。
Jacob Krall 2014年

2
这实际上不是完成工作的原因。在关闭时,易碎队列上还有一个超时被清空。此代码失败的原因是,它不断向该队列添加新对象。
汉斯·帕桑

只考虑这一点,就不会像“另外,如果调用所有对象的Finalize方法花费40秒钟以上,那么CLR会杀死进程一样,”清空易碎队列。
Eric Sc​​herrer 2014年

23

终结器不在主线程中运行。终结器具有自己的运行代码的线程,而不是使应用程序保持运行的前台线程。主线程会立即有效完成,这时终结器线程将尽可能简单地运行尽可能多的次数,直到过程被拆除。没有什么可以使程序继续运行。


如果由于没有主线程处于活动状态而在退出程序40秒后未完成终结器,则终结器将终止,并且过程将终止。尽管这些都是旧值,所以Microsoft可能现在已经调整了实际数字甚至整个算法。Se blog.stephencleary.com/2009/08/finalizers-at-process-exit.html
Lasse V. Karlsen

@ LasseV.Karlsen这是语言的书面记录,还是MS如何选择将其终结器作为实现细节来实现?我希望后者。
2014年

我也希望后者。我所见到的关于此行为的最正式参考是上面的Eric在他的答案中发布的内容,该内容来自Jeffrey Richter撰写的CLR via C#。
Lasse V. Karlsen 2014年

8

垃圾收集器不是活动系统。它“有时”运行,并且大多按需运行(例如,当OS提供的所有页面都已满时)。

大多数垃圾收集器在子线程中以类似于广度第一代的方式运行。在大多数情况下,回收对象可能需要几个小时。

唯一的问题发生在您要终止程序时。但是,这并不是真正的问题。当您使用kill操作系统时,会礼貌地要求终止进程。但是,当该进程保持活动状态时,可以kill -9在操作系统删除所有控制的位置使用它。

当我在交互式csharp环境中运行您的代码时,我得到了:

csharp>  

1
2

Unhandled Exception:
System.NotSupportedException: Stream does not support writing
  at System.IO.FileStream.Write (System.Byte[] array, Int32 offset, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushBytes () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.FlushCore () [0x00000] in <filename unknown>:0 
  at System.IO.StreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] buffer, Int32 index, Int32 count) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.Char[] val) [0x00000] in <filename unknown>:0 
  at System.IO.CStreamWriter.Write (System.String val) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.Write (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.TextWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.IO.SynchronizedWriter.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at System.Console.WriteLine (Int32 value) [0x00000] in <filename unknown>:0 
  at P.Finalize () [0x00000] in <filename unknown>:0

因此,您的程序由于stdout环境的终止而崩溃。

当删除Console.WriteLine和杀死程​​序。五秒钟后,程序终止(换句话说,垃圾收集器放弃了,只释放了所有内存而没有考虑终结器)。


令人着迷的是,交互式csharp由于完全不同的原因而爆炸。原始程序片段没有控制台写行,我很好奇它是否也会终止。
Michael B

@MichaelB:我也对此进行了测试(请参阅下面的评论)。它等待五秒钟,然后终止。我想P一审的终结者只是超时了。
Willem Van Onsem 2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.