当前位置:  开发笔记 > 运维 > 正文

如何在多线程环境中实现垃圾收集?

如何解决《如何在多线程环境中实现垃圾收集?》经验,为你挑选了2个好方法。

我应该如何在包含多个线程或进程的程序中执行垃圾收集?

如何从每个线程和进程中扫描堆栈?

每个进程都需要自己的垃圾收集例程吗?在与实际程序不同的线程/进程中运行垃圾收集器是一个好主意吗?



1> Hrvoje Prgeš..:

大多数GC都被称为"停止世界"GC - 在某些预定义点("Gc点" - 例如调用点,跳转,返回),每个线程将检查是否存在其他想要进行GC循环的线程.一旦所有线程都停止,GC循环就会运行.

当然,还有其他可能性 - 实时,增量,并发(以及更多)GC也存在 - 搜索网络(您大多会发现已发表的论文)或只是购买一本关于GC的书

至于堆栈扫描,有几种方法:

精确扫描:

标记堆栈 - 你基本上保持2个堆栈 - 一个有值,一个有"标签".什么标签取决于你需要什么,但它主要是"是参考"/"不是参考"标记

没有标签的堆栈 - 基本上如果你有一个严格类型的语言,你可以在每个时间点(但更常见的是在每个"GC点")知道堆栈的类型是什么.我将给你一个简单的解释器使用的例子(我刚刚编写):

no-return function XY (int):
 load_param 1 
 ipush 1
 iadd
 call Z (assume: int function Z (int, int))
 new some_object
 call Y

如果我们将GC poins定义为call/new,那么我们可能需要知道我们的堆栈类型有3个点(在XY函数入口处,堆栈被认为是"空"):

调用Z - load_param加载一个int参数,ipush在堆栈上推送一个int - 2 ints,这里我们的GC没什么

new - 调用Z使用堆栈中的2个整数并放置一个int

调用Y - new放置了一个引用,所以现在我们有一个int和一个引用,GC必须知道该引用

请注意,我说在函数入口处堆栈是"空的" - 它实际上不是,但你可以单独分析每个函数,然后向上移动"调用堆栈"(你有一个返回指针,所以你知道你在哪里必须返回 - return-1是一些调用,你也可以获得堆栈的图像.重复直到你到达顶部).

有两种方法可以记住此信息(对于未标记的堆栈):

为每个GC点生成一个表

为每个GC点生成一个代码片段(代码片段本身将标记引用的对象)

至于何时收集此信息,可以预编译或及时.

这也可以应用于机器堆栈,但事情变得有点复杂,因为您可能还必须跟踪寄存器.

您应该能够在网上找到一些关于无标签堆栈的好文章.

您可以进一步修改例如."liveness"信息到您的数据(好吧,堆栈上有一个引用,但如果没有代码向下使用它的指令流,我现在可以将它视为无法访问)

保守的集合(而不是精确的扫描) - 你在问自己"这是堆栈上的这个值,被解释为一个指针,一个引用",如果它是"活着的".如果有例如,这当然可以泄漏.一个看起来像指针的整数(如果整数发生变化,内存将被释放,但也可能永远韭菜).大多数c/c ++收藏家都是以这种方式实现的,所以如果你好奇的话就要朝那个方向搜索.

每个进程都需要它自己的垃圾收集例程吗?

没有什么需要这个,但我会说它很常见 - 为了简单起见,我的每个进程都有一个不同的GC实例(但对于所有线程只有一个).我想在进程之间可能存在共享内存分配器 - 我看到的唯一好处是可能更好地管理内存碎片(因为你控制更多内存)但复杂性(整合通信/同步 - yuck),共享数据量缺乏独立性成为问题.我在这里猜测,我对这种类型的GC没有真正的经验(或者即使它们存在) - 这对我来说似乎是常识.

在与实际程序不同的线程/进程中运行垃圾收集器是一个好主意吗?

这得看情况.将它保存在另一个线程中应该是一个好主意,但是你得到的结果取决于 - 你想让GC保持简单("停止世界" - 所有其他线程都暂停了所以它几乎无关紧要我们执行GC的哪个线程,但如果它有自己的线程则更好,或者你有特殊要求(例如你的线程是实时的,不应该停止很长一段时间,那么你将有一个不同的GC线程并使用实时/增量GC算法).

这一切都取决于你需要什么,但无论你做什么,记住 - 保持尽可能简单.

我差点忘了,从头开始编写自己的GC有一些很好的选择 - 例如.参见LLVM,他们声称"利用这些工具,可以直接为您的运行时发送类型精确的堆栈映射,只需~100行C++代码." (仅预编译代码,没有代码JIT生成支持,如喷射).此外,您可以查看一些Java VM的一些代码(例如,phoneME Advanced VM(CVM),并且就我记忆而言,kaffe非常易读).

免责声明:我曾经(作为一个学生项目)实施了一个精确的,停止世界,无标记,标记和扫描GC - 这里写的所有内容都是我从经验中记得的结果,应该是正确的,但它可能不是"最佳实践".我们欢迎更正.



2> doppelfish..:

您必须为每个地址空间收集一次.多个线程在同一个地址空间中运行,因此需要有一个GC来处理这个问题.对于通过生成不同进程来运行的程序,每个进程都在自己的地址空间中运行,每个进程可以有一个GC.

在多线程的情况下,将GC作为另一个线程运行是有意义的.这样,您可以调整该线程的优先级,以确保整个程序的大致平稳运行.对于单线程进程,最简单的方法是将GC挂钩到"正常"的内存管理例程(特别是分配),但是你可以有两个线程 - 一个用于原始进程的线程和一个GC线程 - 到再次,确保半光滑的性能.

世界上最先进的收藏家是最简单的 - 标记和扫描,标记和紧凑,停止和复制,你的名字.在单线程程序中挂钩到内存管理例程的GC本质上是世界性的.在多线程情况下,线程调度程序可以为GC线程赋予特殊权限(一旦它决定运行就使其不可中断),这允许您从这样的特权线程运行一个停止世界的GC.

如果GC可以被mutator中断(即程序的其余部分),特别是因为它只是另一个根本没有得到特殊处理的线程,你需要一个可以处理mutator干扰的GC.在这种情况下,您正在查看增量GC.IGC可用于单线程,钩入内存管理例程设置,可以中断,即通过超时来确保整个系统的运行有些平稳; 它也可以用在多线程系统中,它只是与其他线程竞争运行时间.

如何找到程序的堆栈或程序的所有线程我都无法告诉你,但是应该为每个操作系统记录这些结构的布局.抓住Boehm GC并扫描源提示可能是有意义的.

正在向您发送Jones/Lins,请花一些时间在http://www.memorymanagement.org/上.有关更多信息,正如Charly Martin上面所说,Sun的人们在垃圾收集领域做了一些惊人的研究和开发,就像IBM Jikes RVM团队的成员和同事一样.

编辑: 在阅读您对Charlie Martin的评论之后,让我给您一个更为温和的建议:将Boem GC连接到您的系统并完成它.编写垃圾收集器很容易.编写一个正确,高效,快速,良好的性能,良好调整和健壮的垃圾收集器几乎是不可能的.使用现有的GC并转到项目的有趣部分.不要被GC困住,或者更糟糕的是,有一个糟糕的GC实现会让你烦恼不已.


.编写垃圾收集器很容易.编写一个正确,高效,快速,良好的性能,良好调整和健壮的垃圾收集器几乎是不可能的.使用现有的GC并转到项目的有趣部分.不要被GC困住,或者更糟糕的是,有一个糟糕的GC实现会让你烦恼不已.
Boehm GC在几天或几周内没有达到那么好.它由该领域的专家撰写.另请注意,SUN和IBM都有一整套专家致力于他们的JVM的GC.
推荐阅读
小妖694_807
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有