CVM Object Allocation

来源:互联网 发布:微商网络推广方案ppt 编辑:程序博客网 时间:2024/05/17 18:18

原文链接:http://weblogs.java.net/blog/mlam/archive/2008/06/cvm_object_allo.html#more

 

CVM 对象分配

 

在之前的评论中,Jamsheed问道:

“在CDC中有快速锁竞争情况下的垃圾收集调用(从我的理解来看就是轮询不安全对象分配线程使之成为垃圾收集安全的线程)。我的问题是为什么一定要等到达到一个安全点之后才进行垃圾回收的调用而不是在一个gc安全窗口中获得一个堆的锁(稍微修改一下gc安全窗口)。或者在每次迭代到达安全点之后轮询尝试获得堆锁。”

 

Jamsheed,我假定你是指那段要求所有线程达到GC安全状态的分配的代码。你可能认为那是个相当慢的过程,应该有代价更低的替换代码,而为什么还要那样做?

 

下面就是理由

 

一些背景

对于那些不知道我们在讨论什么的人来说,这段代码可以在多处被找到。其中一处在gc_common.c文件中的为新对象分配内存的函数中。那里有一处对微型锁的测试。如果微型锁现在并未被当前线程所持有,那么分配的代码就要求所有的线程达到一个GC安全状态。也就是经常在GC讨论中所指的“stop the world”。

 

在许多常见的GC算法中(如在CVMGC)GC需要扫描整个VM中的所有对象指针以确定哪些对象仍可触及因而不应被垃圾收集。为了做到这一点必须确保正在工作的所有线程不能在GC不知道的情况下随便移动对象的指针。这些线程被称为mutator线程,因为它们会mutate(也就是改变)线程中的指针的状态。这是一个对mutation(变化)过于简化的描述,但已足够描述这一点了。

 

所谓GC安全状态是指线程已约定不会改变任何对象指针。因此,当我们需要GC时,首先必须使所有线程达到一个GC安全状态。当所有的线程都各自达到它们的GC安全状态时,就称为到达了一个GC安全状态。按照定义,它们不会再改变线程(至少它们不会妨碍GC)。所以,更准确地说,GC已经“stopped the world至少在以后被通知之前不做任何改变。线程仍可以运行并执行它们的工作就是不能改变对象的指针。如果线程要改变任何对象指针,它就会被阻塞知道GC允许它继续执行。

 

那么这与对象分配有什么关系?

 

快速分配

在一个连续的空闲堆内存区域最快的分配对象的方法就是简单地增大堆顶指针。本质上就是保存一个指向堆顶的指针。这个指针指向堆中已被分配的内存的最高地址。当需要一个新的分配时,简单地将指针加上需要分配的内存的大小。指针之前的值就是新分配的内存的地址。

 

然而这只有在仅一个线程执行所有的分配工作时才可行。如果有多于一个线程,必须保证这些线程不会同时改变指针的值。为做到这一点,CVM(在多数目标平台上)使用了自旋锁。自旋锁的实现是通过使用一个原子交换指令。原子交换指令就是在检测标志值的同时锁住标志。

 

多数时刻,不同的线程并不同时在分配内存。因此,请求微型锁标志的线程一般都会成功获得它,增大堆顶指针值,完成分配并释放微型锁标志。

 

听起来不错,但当多个线程同时在堆上分配内存时该怎样呢?

 

慢速分配

当多个线程同时竞争分配内存时,第二个线程,T2,尝试获取微型锁标志时就会被阻塞直到第一个线程释放锁标志。注意:微型锁标志只是一个标志域而不是一个互斥变量。所以并无与它相关的阻塞功能。期望的是T2被阻塞直到T1完成分配然后T2被唤醒并得到分配堆的控制权。

 

一种解决办法是当T2请求时,“stop the world”,停止所有线程。在T2请求之后,它被阻塞,等待所有线程达到它们的GC安全状态。同时,T1在非GC安全状态下执行内存分配。最终,所有的线程达到它们各自的GC安全状态,唤醒T2。此时,T2可以高枕无忧地执行它的内存分配而不必担心来自其它线程的竞争,因为它们都已经停止了。

 

总之:快速分配的情况仅可能发生在一个线程的非GC安全状态时。如果T2提出一个“stop the world”使所有的线程进入GC安全状态,那么可以保证没有其它线程同时在快速分配。且由于T2是唯一成功地请求了“stop the world”,没有其它线程同时在请求“stop the world”。这确保了T2是进行慢速分配的唯一线程。

 

所以,“stop the world”请求是堆资源分配时线程同步的机制。

 

回到Jamsheed的问题上来

 

为什么不使用一个互斥变量?

 

原因是相对自旋锁来说,互斥变量是重量级的机制。如果仅是参考gc_common.c中分配的C代码这还不是那么明显。然而,快速分配有一个用于JIT编译后的汇编代码。通过这个快速分配的版本可以编译后代码执行。如果使用C代码编译后的版本则会增加许多额外的代码。JIT编译后的代码检测自旋锁时更为容易,但它不能像C版本那样使用系统的互斥变量(它是独立于平台实现的)

 

因此,使用自旋锁标志而非真正意义上的互斥变量是为了使JIT编译后的代码执行效率更高。正如之前指出的,多数情况下竞争不会发生,可以继续使用快速分配。但是在少数情况下当竞争发生时,会切换到C代码执行慢速分配,请求“stop the world”同步所有线程。

 

总结

stop the world”相对于互斥变量来说是重量级的,但它只在不常发生的慢速分配中使用。同时,“stop the world”机制允许我们使用自旋锁标志作为简单的竞争检测在多数快速分配的情况下使用。就总体架构上来说,这中方式比在多数情况下直接使用较慢的互斥变量有更高的效率(与自旋锁检测相较来说

原创粉丝点击