垃圾收集 - 标记+扫描必须是单片/原子

时间:2018-05-20 22:09:58

标签: python memory garbage-collection micropython

本讨论将使用micropython代码,但由于它非常简单,我希望它对mark + sweep

的一般性讨论有用。

Micropython使用垃圾收集,特别是标记和扫描;让我们来定义。

garbage collection

标记
标记阶段,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;之前的时间量。国家不会改变。

扫描期间取消分配

如果一个块在清除器检查之前释放,则不会发生任何特殊情况。 免费操作会将目标区块的状态从标记为更改为免费,然后当清扫程序到达时。 。 。没什么可看的,只是一个免费区块。

如果清单检查后阻止释放免费操作会将目标阻止状态从已用更改为自由

问题

我的分析是否正确:是否可以拆分标记+清除垃圾收集?

1 个答案:

答案 0 :(得分:1)

是的,这是可能的。

自版本1.4(2002)以来,Java有一个Concurrent Mark-Sweep (CMS)收集器。它与你描述的方式类似。

如果你运行Jython,我想你今天可以原生利用它。