GC行为和CLR线程劫持

14
我在阅读《CLR via C#》一书中关于GC的内容,具体是当CLR想要开始垃圾回收时。我知道在进行回收之前必须暂停线程,但它提到必须在线程指令指针到达安全点时才能这样做。在不安全点的情况下,它会尝试快速到达一个安全点,并通过“劫持”线程(在线程堆栈中插入特殊函数指针)来实现。这很好,但我认为默认情况下托管线程是安全的,难道不是吗?
起初我以为它可能是指非托管线程,但CLR让非托管线程继续执行,因为任何正在使用的对象都应该被固定。
那么,在托管线程中,什么是“安全点”,GC如何确定它的位置?
编辑:
我认为我没有说得够清楚。根据此MSDN文章,即使调用了Thread.Suspend,线程也不会真正暂停,直到达到“安全点”。它进一步声明,“安全点”是线程执行的一个点,可以在其中执行垃圾回收。
我觉得我的问题表述不够清晰。我意识到线程只能在安全点暂停,它们必须被暂停以进行GC,但我似乎找不到关于什么是安全点的明确答案。什么决定代码中的一个点是安全的?

2
安全由CLR提供,而不是CLR本身。 - H H
2个回答

15
“安全点”指的是我们处于以下状态:
  1. 不在catch块中。
  2. 不在finally块中。
  3. 不在lock块内。
  4. 不在P/Invoke调用中(在托管代码中) 不在CLR中运行未托管的代码。
  5. 内存树可遍历。
第5点有点令人困惑,但是有时候内存树可能不会被遍历。例如,在优化后,CLR可能会新建一个对象并且没有直接将其分配给变量。根据GC,此对象将是待回收的无效对象。编译器会指示GC发现这种情况时先不要运行GC。
这里有一篇msdn博客文章,提供了更多信息:http://blogs.msdn.com/b/abhinaba/archive/2009/09/02/netcf-gc-and-thread-blocking.aspx 编辑:嗯,对于第4点我错了。请参见这里中的“安全点”部分。如果我们在p/invoke(未托管)代码段中,则允许其一直运行,直到再次返回托管代码。
但是,根据这篇MSDN文章,如果我们在CLR代码的未托管部分,则不被认为是安全的,他们将等到代码返回到托管代码中再执行GC。(至少我很接近了。)

啊,那就说得通了,特别是第五点,整个内存结构必须处于可验证的状态,以便 GC 运行收集。当你有答案时,这是一个非常简单的概念,只是在理解之前我遇到了困难。谢谢! - Christopher Currens
这是我目前在网上找到的关于gc安全点的最详细的答案。感谢Dan!话虽如此,我仍然想补充一下:为什么1/2/3不被视为安全点? - Weipeng

0

实际上,到目前为止我在SO上找到的答案都没有解释“为什么”,也就是说,是什么让代码中的某个点不安全。从我在《pro .NET内存管理》中所了解的来看,答案似乎是:原则上,只要JIT生成的GCInfo完全描述了代码中给定点的GC根,那么代码中的每个点都可以是安全点。

然而,对于每个指令生成GCInfo既不切实际(考虑内存开销),也不必要(因为真正重要的是“到达安全点的时间”(TTSP),只需以足够小的粒度生成安全点即可使TTSP延迟非常小)。因此,JIT编译器使用一些启发式方法来决定生成安全点的频率,以便在内存开销(不要太频繁)和由于TTSP延迟导致的gc延迟之间进行权衡(不要太少)。大多数情况下,仅仅依靠方法调用站点作为安全点就足以使TTSP延迟非常小。其中一个例外是没有进行方法调用的紧密循环,在这种情况下,JIT可能会决定在循环重复边界处注入安全点。

总之,从根本上讲,没有什么会使代码中的特定点对GC“不安全”。这只是JIT决定插入安全点的频率的权衡问题。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接