我正在观看视频Google IO 2008 - Dalvik Virtual Machine Internals以了解Dalvik VM如何工作以及为什么这些人更喜欢Dalvik VM而不是JVM for android。我发现android使用单独的内存来获取有关对象的垃圾信息,与JVM相反,我们有标记位(比特可以判断对象是否能够进行garbagfe收集)以及对象。
有人能详细告诉我,为标记位分别设置内存并且没有单独的标记位存储器有哪些优点和缺点?
我无法通过观看视频来获得这种差异。
答案 0 :(得分:3)
单独位图的一些优点:
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显然非常快。因此,使用单独的标记位可以更快地扫描阶段。
但还有另一个皱纹!在任何标记和扫描收集器中,运行时间由标记阶段支配,而不是通常非常快速的扫描阶段。所以判决结果仍然存在。有些人更喜欢单独的标记位,而另一些更喜欢对象标记位。据我所知,还没有人能够证明哪种方法优于另一种方法。