这个异步垃圾收集器的可能陷阱

时间:2011-04-16 03:37:49

标签: multithreading algorithm concurrency garbage-collection nonblocking

我一直在研究垃圾收集,主要应用于服务器端/实时应用程序,我已经开始绘制一个算法,用它可以有一个异步垃圾收集系统。由于我现在开始讨论这个主题,所以我不太了解gc算法,我想知道这样的实现可能存在的缺陷。该算法非常粗糙,并且有许多未定义的部分。

以下是我的想法:

  • 每个线程都有自己的堆空间来管理,并存储其拥有的其他线程正在使用的指针列表 有了它,垃圾收集与正在运行的线程完全异步,并且:
  • 阶段1开始跟随线程的根并标记它们可以访问的所有对象。如果我们进入另一个线程的空间,我们停止跟随这个指针,并在所有者线程上将此指针标记为“正在使用”
  • 在我们标记了所有区域之后,我们选择一个区域(可能具有尽可能多的死引用),并开始将其活动对象引用复制到另一个空间。 (它可能是整个线程堆空间,但我认为这可能是内存密集型操作)
  • 复制开始于使用CAS设置一个标志,表明正在复制对象。在设置该标志时对该特定对象执行的任何可变动作将旋转锁定,直到gc线程设置新地址。复制完成后,旧地址设置一个新地址,并且要对该对象执行的任何可变引用将被路由到新对象
  • 在使用CAS更新对这些指针的所有引用之后,最终释放旧空间(不会使用错误的地址更新新指针,因为每个mutator将首先检查引用是否已更改位置)

就是这样!

无论如何,我对一个不会停止世界的可能实现感到非常兴奋,并且只使用仅适用于被复制对象的快速自旋锁。但是我想知道这是否可以实现,或者是否有可能在某处有悬挂指针,或者内存泄漏,或者它是否有效,等等。任何有助于此的信息都将非常感激!

我不太清楚如何例如它将处理来自不同线程的循环引用。我认为这将自然处理,因为我们更新了当前gc'ed线程所具有的所有危险指针。 可能还有一些我没有考虑的并发访问。

谢谢!

----编辑: 感谢Ernest的贡献,我正在考虑不使用复制算法,但可能是一个简单的标记&扫。那是因为如果指针已经更新,我们每次访问对象的变量时都需要检查。这在我看来是一个相当大的开销。不是吗?

3 个答案:

答案 0 :(得分:1)

我看到的主要问题是同步。你需要内存障碍来确保各个线程能够在彼此的堆上看到最新的数据,而且我看不到它们真正存在的位置,仍然保持完全异步的操作模型。

答案 1 :(得分:1)

刚看到这个http://java-monitor.com/forum/showthread.php?t=890

据我所知,你所说的模型类似于Erlang VM使用的模型 - 每个线程都有自己的堆。 Erlang本质可能 - 不需要自旋锁(至少对于线程堆)。

答案 2 :(得分:1)

您的想法很好,但我相信有许多微妙的问题需要大量的工作才能解决,一旦解决,您将无法获得具有竞争力的吞吐量性能。

  

每个线程都有自己的堆空间来管理,并存储它拥有的其他线程正在使用的指针列表。这样,垃圾收集与正在运行的线程完全异步,并且:

如果有共享对象,哪个线程拥有它?如果并发集合从不同的线程添加了对象,那么会有很多堆间指针吗?你如何处理堆之间的循环?

  

阶段1开始跟随线程的根并标记它们可以访问的所有对象。如果我们进入另一个线程的空间,我们停止跟随这个指针,并在所有者线程上将此指针标记为“正在使用”

如果这与mutator运行同时进行,那么在GC执行此标记时,如何防止mutator更改拓扑以引入或消除堆间引用的竞争条件?

  

在我们标记了所有区域之后,我们选择一个区域(可能具有尽可能多的死引用),并开始将其活动对象引用复制到另一个空间。 (它可能是整个线程堆空间,但我认为这可能是内存密集型的操作)

如果是多核,则复制会使全局内存带宽饱和并破坏可扩展性。

  

复制开始于使用CAS设置一个标志,表明正在复制对象。在设置该标志时对该特定对象执行的任何可变动作将旋转锁定,直到gc线程设置新地址。复制完成后,在旧的地址上设置一个新地址,并且要在该对象上执行的任何可变引用将被路由到新对象

无锁是很棘手的。你正在对内存模型做出假设。您可能需要内存障碍。看起来你只是在模仿一个锁,在这种情况下,你所说的是在每次写入堆的引用时获取并释放一个锁,这将非常昂贵。

自旋锁对于并发性也是不利的,因为你不仅要占用一个线程,还要烧掉一个无法完成你正在等待的工作的核心!请参阅GHC中的last core slowdown错误。等待免费解决方案将使用CAS来获取mutator线程以帮助GC工作,而不是阻止等待另一个线程执行它。

  

在使用CAS更新对这些指针的所有引用之后,最终释放旧空间(不会使用错误的地址更新新指针,因为每个mutator将首先检查引用是否已更改位置)

确定。