将标记位组合在一起并将其与垃圾收集分开的优点和缺点是什么

时间:2014-04-14 10:16:08

标签: android garbage-collection mark-and-sweep

我正在观看视频Google IO 2008 - Dalvik Virtual Machine Internals以了解Dalvik VM如何工作以及为什么这些人更喜欢Dalvik VM而不是JVM for android。我发现android使用单独的内存来获取有关对象的垃圾信息,与JVM相反,我们有标记位(比特可以判断对象是否能够进行garbagfe收集)以及对象。

有人能详细告诉我,为标记位分别设置内存并且没有单独的标记位存储器有哪些优点和缺点?

我无法通过观看视频来获得这种差异。

2 个答案:

答案 0 :(得分:3)

单独位图的一些优点:

  • 更密集。典型的GC需要8位GC元数据,但由于对齐,对象内的标头可能会将此内存最多绕32位。
  • 某些操作,尤其是扫地操作,变得更快。这部分是因为更密集(见上文)位图意味着更少的内存流量和更好的缓存使用,但也因为在这种格式下可以对某些操作(例如将所有标记位置零)进行矢量化。 (需要设计GC的其他部分以利用该能力。)
  • 如果您在Unix系统上fork(),则单独的位标记可以更好地利用写入时复制:包含对象的页面可能仍然是共享的。

对象内标记位的一些优点:

  • 根据用于将对象与位图关联的方案,获取对象的标记位(反之亦然)可能非常复杂和/或很慢。另一方面,对象头部很容易访问。
  • 更简单的内存管理:无需创建正确大小的单独分配并保持同步。
  • 许多用于查找对象位图的快速方案(反之亦然)在其他方面具有相当大的限制性。例如,如果为每个页面创建一个位图并将位图指针存储在页面的开头,那么存储大于页面的对象时会出现问题。

答案 1 :(得分:1)

单独的标记位通过具有位数组来工作,其中每个位表示堆中可以启动对象的地址。例如,假设堆是65536字节并且所有对象都以16字节边界对齐,那么堆中有4096个地址可以作为对象的开始。这意味着数组需要包含4096位,可以有效地存储为512字节或64位64位无符号整数。

对象标记位的工作原理是,如果对象被标记,则每个对象的每个标题的一位被设置为1,否则为0。请注意,这要求每个对象都有一个专用的标题区域。诸如JVM和.NET之类的运行时都会向对象添加标头,因此您基本上可以免费获得标记位的空间。

但对于那些不能完全控制他们所处环境的保守收藏家来说,它并不起作用,例如Boehm GC。他们可能会错误地将整数标识为指针,因此对于他们修改mutators数据堆中的任何内容都是有风险的。

Mark&扫垃圾收集分为两个阶段:标记和清扫。使用对象内标记位进行标记是直接的(伪代码):

if not obj.is_marked():
    obj.mark()
    mark_stack.append(obj)

使用单独的数组存储标记位,我们必须将对象的地址和大小转换为位数组中的索引,并将相应的位设置为1:

obj_bits = obj.size_in_bytes() / 16
bit_idx = (obj - heap.start_address()) / 16
if not bitarr.bit_set(bit_idx):
    bitarr.set_range(bit_idx, obj_bits)
    mark_stack.append(obj)

因此在我们的示例中,如果对象长度为128个字节,则将在位数组中设置8位。显然,使用对象内标记位要简单得多。

但扫描时,单独的标记位会获得一些动力。扫描涉及扫描整个堆并找到未标记的连续记忆区域,因此可以回收。使用对象内标记位,它大致如下所示:

iter = heap.start_address()
while iter < heap.end_address():
    # Scan til the next unmarked object
    while iter.is_marked():
        iter.unmark()
        iter += iter.size()
        if iter == heap.end_address():
            return
    # At an unmarked block
    start = iter
    # Scan til the next marked object
    while iter < heap.end_address() and not iter.is_marked():
        iter += iter.size()
    size = iter - start
    # Reclaim the block
    heap.reclaim(start, size)

注意迭代如何在iter += iter.size()行中从一个对象跳转到另一个对象。这意味着扫描阶段的运行时间与实时和垃圾对象的总数成比例。

使用单独的标记位,你会做大致相同的循环,除了大量的垃圾对象飞过而没有&#34;停止&#34;他们每个人。

再次考虑65536堆。假设它包含4096个全部为垃圾的对象。迭代标记位数组中的64位64位整数并看到它们全为0显然非常快。因此,使用单独的标记位可以更快地扫描阶段。

但还有另一个皱纹!在任何标记和扫描收集器中,运行时间由标记阶段支配,而不是通常非常快速的扫描阶段。所以判决结果仍然存在。有些人更喜欢单独的标记位,而另一些更喜欢对象标记位。据我所知,还没有人能够证明哪种方法优于另一种方法。