【问题标题】:GC Behavior and CLR Thread HijackingGC 行为和 CLR 线程劫持
【发布时间】:2023-04-06 07:21:01
【问题描述】:

我正在阅读CLR via C# 一书中关于 GC 的内容,特别是关于 CLR 何时想要开始收集的内容。我知道它必须在收集发生之前暂停线程,但它提到当线程指令指针到达安全点时它必须这样做。在它不在安全点的情况下,它会尝试快速到达一个点,并通过hijacking 线程(在线程堆栈中插入一个特殊的函数指针)来实现。这一切都很好,但我认为默认托管线程是安全的?

我最初认为它可能指的是非托管线程,但 CLR 允许非托管线程继续执行,因为任何正在使用的对象都应该被固定。

那么,托管线程中的safe point 是什么,GC 如何确定它是什么?

编辑:

我认为我说得不够具体。根据this MSDN article,即使调用Thread.Suspend,线程实际上也不会被挂起,直到达到safe point。它进一步指出safe point 是线程执行中可以执行垃圾收集的点。

我想我的问题不清楚。我意识到线程只能在安全点暂停,并且必须为 GC 暂停,但我似乎无法找到关于什么是安全点的明确答案。是什么决定了代码中的某个点是安全的?

【问题讨论】:

  • CLR 安全,而不是 CLR。​​

标签: c# garbage-collection clr


【解决方案1】:

“安全点”是我们所在的位置:

  1. 不在 catch 块中。
  2. 不在 finally 中
  3. 不在锁内
  4. 不在 p/invoke'd 调用中(在托管代码中)。不在 CLR 中运行非托管代码。
  5. 内存树是可遍历的。

第 5 点有点令人困惑,但有时内存树是不可遍历的。例如,优化后,CLR 可能会新建一个 Object,而不是直接将其分配给变量。根据 GC,这个对象将是一个准备好被收集的死对象。发生这种情况时编译器会指示 GC 尚未运行 GC。

这是 msdn 上的一篇博文,其中包含更多信息:http://blogs.msdn.com/b/abhinaba/archive/2009/09/02/netcf-gc-and-thread-blocking.aspx

编辑:嗯,先生,我对 #4 的看法是错误的。请参阅“安全点”部分中的here。如果我们在 p/invoke(非托管)代码段内,则允许它运行,直到它再次返回托管代码。

但是,根据this MSDN article,如果我们处于 CLR 代码的非托管部分,则认为它不安全,他们将等到代码返回托管。 (至少我很接近)。

【讨论】:

  • 啊,这很有意义,尤其是#5,整个内存结构需要处于可验证状态,GC 才能运行集合。当你有答案时,这是一个非常简单的概念,它刚刚到达那里,我遇到了麻烦。谢谢!
  • 这是我目前在网上能找到的关于什么是 gc 安全点的最详细的答案。谢谢丹!话虽如此,我仍然想补充:为什么 1/2/3 不被视为安全点?
【解决方案2】:

实际上,到目前为止,我在 SO 上找到的答案都没有解释“为什么”,即是什么使代码中的某个点不安全。为此,从“Pro .NET 内存管理”中读到的内容,答案似乎是:原则上,代码中的每个点都可以是安全点,只要有 GCInfo 生成JIT 全面描述代码中给定点的 GC 根。

但是,它既不切实际(考虑内存开销,我们正在讨论每条 CPU 指令的 GCInfo)和不必要的(因为真正重要的是“到达安全点的时间”(TTSP),这就足够了以使该 TTSP 延迟足够小的粒度生成安全点)为每个结构生成安全点。

因此,JIT 编译器使用一些启发式方法来确定安全点的生成频率,以便在内存开销(不太频繁)和由于 TTSP 延迟(不太少)引起的 gc 延迟之间进行权衡。大多数情况下,仅依靠方法调用站点作为安全点就足够了,因为它们发生的频率足以使 TTSP 延迟非常小。一个例外是紧密循环,在这种循环中不进行任何方法调用,在这种情况下,JIT 可能会决定在循环重复边界处注入安全点。

因此,总而言之,从根本上说,代码中的任何特定点对于 GC 来说都是“不安全的”。决定插入安全点的频率只是 JIT 权衡的问题。

【讨论】:

    猜你喜欢
    • 2023-03-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多