CLR Via C#读书笔记——垃圾收集相关知识点【2011-01-20】

来源:互联网 发布:直播软件你懂得 编辑:程序博客网 时间:2024/05/21 05:44

     在现实世界中,经常会出现多个线程同时访问托管堆的情况,或者至少会有多个线程同时操作托管堆中分配的对象。当某个线程触发垃圾收集器时,其他线程就不能再访问任何对象(包括各线程自己堆栈上的对象引用),这是因为垃圾收集器可能会移动这些对象,改变它们内存地址。

     因此当垃圾收集器开始运行时,所有执行托管代码的线程都必须被挂起。CLR使用几种不同的机制来确保安全的挂起线程,以便执行垃圾收集。CLR中存在多种机制的目的是为了尽可能地让线程保持运行,并且尽可能地减少开销。

     当CLR开始执行垃圾收集时,它会立即挂起进程中所有正在执行托管代码的线程。接着,CLR会检查每个线程的指令指针以判断线程线程执行到哪里。然后,CLR将指令指针地址和JIT编译器产生的表做比较,从而确定线程正在执行的代码。

     如果线程的指令指针位于某个表中标记的偏移之处,那么该线程将被认为是到达了一个安全点(safe point)。一个安全点指的是可以在垃圾收集执行完之前一直挂起线程的地方。如果线程的指令指针没有位于某个内部方法表中标记的偏移之处,那么该线程就没有到达一个安全点,CLR也就不能执行垃圾收集。在这种情况下,CLR会劫持该线程:CLR会修改线程的堆栈,以使它返回的地址指向CLR内部实现的一个特殊函数。然后,该线程将被允许继续执行。目前正在执行的方法返回时,CLR内部的特殊函数将执行,从而挂起该线程。

     然而,线程有时候并不能很快从当前的方法中返回。所以在线程继续执行后,CLR会等待被劫持的线程大约250毫秒。过了这个时间之后,CLR会再次挂起线程,并检测其指令指针。如果线程达到了一个安全点,垃圾收集就可以开始执行了。如果线程仍然没有到达一个安全点,CLR将检测该方法内部是否又调用了其他的方法。如果确实调用了其他的方法,CLR将再一次修改线程的堆栈,以使线程在最近执行的方法中返回时能被劫持。然后,线程被允许继续执行,CLR会再等一段时间,之后会再次尝试劫持线程。

     当所有的线程都到达了一个安全点或者被劫持后,垃圾收集才可以开始执行。当垃圾收集完成后,所有的线程将被允许恢复执行,应用程序也将继续运行。被劫持的线程也会返回到原来调用它们的方法中。

    前面提到的算法还有一个小问题。即当CLR希望执行垃圾收集时,CLR就会挂起所有正在执行托管代码的线程,但是CLR没有挂起那些正在执行非托管代码的线程。一旦所有正在执行托管代码的线程到达了一个安全点或者都被劫持,CLR就允许垃圾收集开始执行。而执行非托管代码的线程允许继续执行,因为非托管代码正在使用的所有对象都是已经被固定了。如果一个正在执行非托管代码的线程返回托管代码,那么该线程就会被立即挂起,直到垃圾收集完成。

     最终结果证明,CLR大部分时候都是使用劫持来挂起线程,而不是根据JIT编译器生成的表来判断线程是否到达了一个安全点,然后再决定挂起线程。这样做的原因在于,使用JIT编译器生成的表时需要更多的内存,并且增加了进程的工作集,这最终会极大地影响垃圾收集的性能。因此,JIT编译器生成的表中包含的信息适用于这样的场合:方法中包含循环语句,并且该循环中不调用其他的方法。如果方法中的循环调用了其他方法,或者方法没有循环,那么JIT编译器生成的表中就没有多少信息,因此CLR还是使用劫持来挂起线程。

原创粉丝点击