再说说.NET的GC机制——何时执行清理

将近一年前的.NET的GC机制和GC知识学习提到过:

另外,GC上进行各种操作的部分应该是多线程进行的,为了保证对象安全性,在GC运行的时候.Net会直接Stop the World进行操作,一般来说是中断,但也有用到Richter大神那篇文章里面提到的其他机制,例如Hijacking和Safe Point等等。

那么Hijacking和Safe Point这两种机制到底是怎么回事?简单来说Fully Interruptible Code提供的是宏观上“如何处理”的机制(直接挂起所有threads),而前面提到的这两种机制则分别提供具体的处理方法。

首先考虑,何时Stop the world?参考MSDN上Thread.Suspend的说明,其中提到:

When you call ThreadSuspend on a thread, the system notes that a thread suspension has been requested and allows the thread to execute until it has reached a safe point before actually suspending the thread. A safe point for a thread is a point in its execution at which garbage collection can be performed.
当在线程上调用Thread.Suspend时,系统会注意到线程挂起的请求,并且在实际挂起此线程前,允许它继续运行——除非线程来到了safe point。safe point是执行中可以进行GC的时间点。

很清楚,也就是说在GC开始前,线程的挂起点必须是safe point。在Thread.Suspend的说明下面还有个醒目的Note:

In order to perform a garbage collection, the runtime must suspend all the threads except the thread performing the collection. Each thread must be brought to a safe point before it can be suspended.
为了进行GC,运行时必须挂起除了调用GC所在线程外的所有线程。每个线程在挂起前都必须走到safe point。

Safe point的判定条件,是“当前线程的指令指针(IP)不在internal method table记录的偏移量中”,[1]亦即在进行特殊处理,或者跑到非托管区域去了。对于这类情况,CLR会进行Hijacking。在GC Behavior and CLR Thread Hijacking这个问题的唯一答案内,回答人给出了几种更为具体的safe point必要条件

  • 不在catch或finally块中
  • 不在lock中
  • 没有运行非托管代码
  • 内存树可以走通(walkable)

在非safe point情况下,CLR便采用Hijacking方式,顾名思义即对当前执行函数的上下文进行“劫持”。通过修改线程调用栈,.NET会在必要的函数结束的时间点插入一个特殊函数,它用来挂起当前线程,以便开始GC动作。GC结束后,此函数将运行权放回,后续函数可以继续执行[2]。Hijacking开始后,CLR会等待250ms,接着再次检查是否线程已经运行到了safe point,如果还没到那么接着进行Hijacking。[1]

这一机制主要处理的是就是上面列出的几种被判定为非safe point的情况,尤其是非托管对象的运行:在特殊函数执行,或者说thread suspension状态时,非托管对象可以继续执行,它和GC动作互不影响。被非托管对象占用的资源不会在托管堆上;而在非托管对象清理方面,还记得不,对于覆盖了Finalize方法的托管对象,GC会将它放入Freachable Queue变为根对象,GC动作不会直接将其干掉。

另外,针对多处理器的情况,CLR还准备了Synchronization-free Allocations和Scalable Collections两种机制,利用并行处理最大限度提高GC效率和UI线程的响应性。两者都是将托管堆切分成数个区域,由多线程进行并行处理。不同的则是前者是workstation GC,多个线程跑在同一个CPU上;后者是server GC独享,每个CPU运行一个线程。[2]

参考资料

1. 《CLR via C#》,Jeffrey Richter
2. Garbage Collection: Memory Management in .Net, Ritesh Ratna