本讨论将使用micropython代码,但由于它非常简单,我希望它对mark + sweep
的一般性讨论有用。Micropython使用垃圾收集,特别是标记和扫描;让我们来定义。
标记
在标记阶段,gc
跟随内存引用,字面标记使用的内存块,表示它们可以从根块集中访问。
扫描
标记阶段完成后,清扫程序在整个堆上循环,如果使用了内存块但没有标记,则意味着代码无法访问它,因此它被释放了#34;即标记为免费。在标记阶段标记的内存块已删除该标记。
当前的实现需要一个原子调用来执行又名gc
的垃圾收集,但是我一直想知道是否可以将它分成多个调用而不是单片/原子调用
这有助于减少抖动:而不是一次大的时间命中,你会发出一堆较小的呼叫。 (此处不讨论您如何展开" gc
来电的实施细节......除非有人认为会增加讨论。)
如果gc
正在运行"在后台" - 中间字节码或after pre-defined bytecodes - 然后在错误的点分配(或解除分配)可能导致竞争条件和堆损坏。在我们分解gc
执行之前,我们必须确定可能的竞争条件。
可以执行的两个操作是:分配和释放。
如果用户在标记或扫描阶段中间执行分配会发生什么?
让我们看一下具体的代码示例
>> var1 = SomeAllocation()
标记期间的分配
在上面的示例中,语句在REPL中执行,因此对字典的任何添加都将是全局字典,这是GC Roots
中的条目。如果在扫描之前将一个条目添加到全局变量中,则不会出现任何错误"发生:新内存块将标记,因为它应该是。
问题是如果在扫描之后修改了全局变量。在这种情况下,内存块不会标记,因此在扫描阶段,它将被视为“无法访问”#34;并释放。 。 。即使它不应该。
扫描期间的分配
如果在清扫程序遍历内存中的该点之前分配了一个块,它将释放它,因为它没有来自标记阶段的特殊标记。如果在清扫程序遍历所述块之后分配了一个块,则不会发生任何不良情况。
解决方案
如果gc
正在执行中,请将已分配的块标记为标记为。唯一的缺点是,如果您在相位扫描中进行分配,并且在扫描者检查了新分配的块后,您将使用标记为标记的块完成gc
。除非用户明确释放它,否则如果无法访问,您需要经过额外的gc
周期才能释放它。
但是有一个简单的解决方案:如果你在相位扫描期间进行分配,则检查清扫器的位置:如果要在其后面分配新块,则不要用<标记< strong>标记,否则,请使用标记对其进行标记,因为清扫工具会删除标记。这样,您就不会使用标有标记的块来退出gc
。
如果用户在标记或扫描阶段中间执行分配会发生什么?
标记期间的重新分配
如果在扫描引用(父)块之前块已释放,则不会发生任何事情。
如果某个区块已释放且其子级已经标记为,则我们会出现不一致的情况,因为只有标记为他们的父母也是标记(或父母是GC root
)。结果是这些无法访问但标记的块在额外的gc
周期之后才会被释放,因为已经标记,这些无父级但标记块不会通过相位扫描释放。
但是,我不认为这是一个问题,因为这与单片gc
的情况不同。在单片gc
中,您必须完成当前gc
周期,然后用户会调用free(ptr)
,然后该块的孩子将在下一个gc
中被释放{1}}。堆在#34;正确&#34;之前的时间量。国家不会改变。
扫描期间取消分配
如果一个块在清除器检查之前释放,则不会发生任何特殊情况。 免费操作会将目标区块的状态从标记为更改为免费,然后当清扫程序到达时。 。 。没什么可看的,只是一个免费区块。
如果清单检查后阻止释放,免费操作会将目标阻止状态从已用更改为自由
问题
我的分析是否正确:是否可以拆分标记+清除垃圾收集?
答案 0 :(得分:1)
是的,这是可能的。
自版本1.4(2002)以来,Java有一个Concurrent Mark-Sweep (CMS)收集器。它与你描述的方式类似。
如果你运行Jython,我想你今天可以原生利用它。