Incermental GC
增量式垃圾回收
增量式垃圾回收 Incremental GC
一种通过逐渐推进垃圾回收来控制mutator最大暂停时间的方法。
什么是增量式垃圾回收
有时候GC时间太长会导致mutator迟迟不能进行。如下图示:
这样的GC称为停止型GC(Stop the world GC)。
为此出现了增量式垃圾回收。增量(incremental)式垃圾回收是将GC和mutator一点点交替运行的手法,如下图示:
三色标记算法
描述增量式垃圾回收算法使用Edsger W.Dijkstra等人提出的三色标记算法(Tri-color marking)。将GC中的对象按照各自的情况分为三种,使用三种颜色代替。如下
- 白色:还未搜索过的对象
- 灰色:正在搜索的对象
- 黑色:搜索完成的对象
以下使用GC标记-清除算法为示例:
GC开始运行前的所有对象都是白色,GC一旦开始运行,所有能从根到达的对象都会被标记,然后送到栈里。这样的对像是灰色。灰色的对象会从栈中取出,其子对象也会被涂成灰色,当所有子对象全部被涂成灰色,这时该对象就会成为黑色。
GC结束的时候,活动对象全部是黑色,垃圾为白色。
以上仅仅是举例说明,具体用什么标记它的颜色,只要能显示出三种状态就行。具体使用什么方法,自己选。
GC 标记清除算法的分割
GC标记清除算法那增量式运行的三个阶段。
- 根查找阶段:将根直接指向对象标记成灰色
- 标记阶段:将子对象涂成灰色,结束时候所有对象都是黑色
- 清除阶段:查找清除白色对象连接到空闲链表。将黑色对象变为白色对象。
下面是增量式垃圾回收的incremental_gc()函数
incremental_gc(){
case $gc_phase // 检查变量,判断应该进入那个阶段
when GC_ROOT_SCAN // 进入查找阶段
root_scan_phase()
when GC_MARK // 进入标记阶段
incremental_mark_phase()
else
incremental_sweep_phase() //清除阶段
}
- 当进入根查找阶段,我们直接把根引用的对象打上记号,放入栈中。GC开始时运行一次。
- 根查找结束后,incremental_gc()会告一段落,mutator会再次开始运行。
- 下来再次执行incremental_gc(),函数进入标记阶段。在标记阶段incremental_mark_phase()函数会从栈中取出对象和搜索对象。操作一定次数后,mutator会再次开始运行。直到栈标记为空。
- 之后就是清除阶段。incremental_sweep_phase()函数不是一次性清除整个堆,而是每次只清除一定个数,然后中断GC,再次运行mutator。
根查找阶段
根查找阶段非常简单。作为根查找实体的 root_scan_phase() 函数,如代码清单所示:
root_scan_phase(){
for(r : $roots)
mark(*r)
$gc_phase = GC_MARK
}
对能直接从根找到的对象调用 mark() 函数。mark() 函数的伪代码如下所示。
mark(obj){
if(obj.mark == FALSE)
obj.mark = TRUE
push(obj, $mark_stack) }
如果参数 obj 还没有被标记,那么就将其标记后堆到标记栈。这个函数正是把 obj 由白色涂成灰色的函数。
当我们把所有直接从根引用的对象涂成了灰色时,根查找阶段就结束了,mutator会继 续执行。此外,这时 $gc_phase 变成了 GC_MARK。也就是说,下一次 GC 时会进入标记阶段。
标记阶段
incremental_mark_phase(){
for(i :1...MARK_MAX)
if(is_empty($mark_stack) == FALSE) //从栈中取出对象,将其子对象涂成灰色。
obj = pop($mark_stack)
for(child :children(obj))
mark(*child) // 递归涂子孩子。
else
for(r :$roots) // 再次对根直接引用的对象进行标记。因为第一次标记根本没有进行完,而且之后也可能发生变化。
mark(*r)
while(is_empty($mark_stack) == FALSE)
obj = pop($mark_stack)
for(child :children(obj))
mark(*child)
$gc_phase = GC_SWEEP // 为清除阶段做准备
$sweeping = $heap_start
return
}
- 可以看到首先从栈中取出对象,将其子对象涂成灰色。但是这一系列操作只执行了MARK_MAX次。我们知道增量式的垃圾回收不是一次性处理完了。所以这个MARK_MAX就显得格外重要了。
- 之后在标记即将结束前,对根对象指向的对象再次标记。原因如下图示:
我们可以看到由于增量式垃圾回收它是一步一步走的,并不是说一次就把GC做完,所以它在GC的过程中指针时会变化的。如果变化如上图,我们又不对其重新标记,那得到的结果就是,C对象被删掉了。很严重的一个后果啊。
为了防止这样,我们又一次的使用了写入屏障。
写入屏障
看一下Edsger W. Dijkstra 等人提出的写入屏障
write_barrier(obj, field, newobj){
if(newobj.mark == FALSE)
newobj.mark = TRUE
push(newobj, $mark_stack)
*field = newobj
}
如果新引用的对象newobj没有被标记过,就将其标记后堆到标记栈里。
即使在 mutator 更新指针后的图中c,也没有产生从黑色对象指向白色对象的引用。这样一来我们就成功地防止了标记遗漏。
清除阶段
当标记栈为空时,GC就会进入清除阶段。代码清单如下:
incremental_sweep_phase(){
swept_count = 0
while(swept_count < SWEEP_MAX)
if($sweeping < $heap_end)
if($sweeping.mark ==TRUE)
$sweeping.mark = FALSE
else
$sweeping.next = $free_list
$free_list = $sweeping
$free_size += $sweeping.size
$sweeping += $sweeping.size
swept_count++
else
$gc_phase = GC_ROOT_SCAN
return
}
该函数所进行的操作就是把没被标记的对象连接到空闲链表,取消已标记的 对象的标志位。
为了只对一定数量的对象进行回收,事先准备swept_count用来记录数量。swept_count >= SWEEP_MAX 时,就暂停清除阶段,再次执行 mutator。当把堆全部清除完毕时,就将 $gc_phase 设为 GC_ROOT_SCAN,结束 GC
分配
newobj(size){
if($free_size < HEAP_SIZE * GC_THRESHOLD) // 如果分块的总量 $free_size 少于一定的量HEAP_SIZE就执行GC
incremental_gc()
chunk = pickup_chunk(size, $free_list) // 搜索空闲链表返回大小时size的块
if(chunk != NULL)
chunk.size = size
$free_size -= size
if($gc_phase == GC_SWEEP && $sweeping <= chunk) // 判断GC是否在清除阶段和chunk是不是在已清除完毕的空间
chunk.mark = TRUE // 没有在清除完毕的空间,我们要设置标志位
return chunk
else
allocation_fail()
}
- 判断$free_size 是不是小于HEAP_SIZE * GC_THRESHOLD,如果是就执行GC。
- 在空闲链表查找大小为size的分块,并返回。
- 对分块进行标记,对$free_size进行后移操作。
- 判断 GC状态,和chunk状态。
- 如果chunk在清除完毕的空间的空间里什么都不做,如果不在则进行标记。
优点和缺点
缩短最大暂停时间
- 增量式垃圾回收通过交替运行GC和mutator来减少停止时间,减少二者的相互影响。从而保证GC不会长时间妨碍mutator。
- 增量式垃圾回收不是重视吞吐量,而是重视如何缩短最大暂停时间。
降低了吞吐量
- 写入屏障会增加额外负担。但是这是必要的牺牲啊啊啊。
- 高吞吐量和缩短最大暂停时间,二者不可兼得。根据需要选择最合适的最好。
Steele 的算法
1975,Guy.Steele
这个算法中使用的写入屏障条件更严格,它能减少GC中错误标记的对象。
mark()函数
mark(obj){
if(obj.mark == FALSE)
push(obj, $mark_stack)
}
再把对象放入标记栈的时候还没有标记,在这个算法中从标记栈取出时才为它设置标记为。这里的灰色对象时“标记栈里的没有设置标记位置的对象”,黑色是设置了标识位的对象。
写入屏障
write_barrier(obj, fieldm newobj){
if($gc_phase == GC_MARK && obj.mark == TRUE && newobj.mark == FALSE)
obj.mark = FALSE
push(obj, $mark_stack)
*field = newobj
}
- 判断条件,条件成立时,将obj.mark设置为FALSE
- 将obj 放入标记栈。
- 如果标记过程中发出引用的对象时黑色,且新的引用对象为灰色或者白色,那么我们就把发出引用的对象涂成灰色。
如上图示:写入屏障在a到b中发挥了作用。对象A被涂成了灰色,其结果就是c中不存在从黑色对象指向的白色对象,也就不会出现把活动对象标记遗漏的状况了。
当A对象为灰色的时候,我们会再次对A对象进行搜索和标记。
汤浅的算法(不放入cnblog)
汤浅太一,1990 Snapshot GC
这种算法是以GC开始时对象间的引用关系(snapshot)为基础来执行GC的。因此,根据汤浅算法,在GC开始时回收垃圾,保留GC开始时的活动对象和GC执行过程中被分配的对象。
标记阶段
incremental_mark_phase(){
for(i :1..MARK_MAX)
if(is_empty($mark_stack) == FALSE)
obj = pop($mark_stack)
for(child: children(obj))
mark(*child)
else
$gc_phase = GC_SWEEP
$sweeping = $heap_start
return
}
- 在汤浅算法中,清除阶段没有必要再去搜索根了,因为该算法以GC开始时对象间的引用关系为基础执行GC。
- 在标记阶段中,新的从根引用的对象在GC开始时应该会被别的对象锁引用。因此搜索GC开始时就存在的指针,就会发现这个对象已经被标记完毕了。所以没有必要从新的根重新标记它。
从黑色对象指向白色对象的指针
之前我们提到过,使用写入屏障来防止产生从黑色对象指向白色对象的指针。但是汤浅算法中我们允许黑色对象指向白色对象。这样还能回收成功的原因是因为GC一开始就保留活动对象的这项原则。
遵循这项原则,就没有必要在新生成指针时标记引用的目标子对象。即使生成了从黑色对象指向白色对象的指针,只要保留了GC开始时的指针,作为引用目标的白色对象早晚都会被标记。
其实指针被删除时的情况应该引起我们的注意。指向对象的指针删除,就可能无法保留GC开始时的活动对象了。因此在汤浅的写入屏障中,再删除指向对象的指针时要进行特殊处理。
写入屏障
write_barrier(obj, field, newobj){
oldobj = *field
if(gc_phase == GC_MARK && oldobj.mark == FALSE)
oldobj.mark = TRUE
push(oldobj, $mark_stack)
*field = newobj
}
当GC进入到标记阶段且oldobj是白色对象,就将其涂成灰色。
图b转移到图c的过程中写入屏障发挥了作用,他把c涂成了灰色,这样就防止c的标记遗漏。
图b中,黑色对象指向了白色对象。但是B指向C并没有被删除。在汤浅的写入屏障中这时候不会进行特殊的处理。只有当B指向C的指针被删除的时候,C才会变为灰色。
分配
newobj(size){
if($free_size < HEAP_SIZE * GC_THRESHOLD)
incremental_gc()
chunk = pickup_chunk(size, $free_list)
if(chunk != NULL)
chunk.size = size
$free_size -= size
if($gc_phase == GC_MARK)
chunk.mark = TRUE
else if($gc_phase == GC_SWEEP && $sweeping <= chunk)
chunk.mark = TRUE
return chunk
else
allocation_fail()
}
在标记阶段进行分配时会无条件设置obj的标志位。也就是说,会把obj涂成黑色。汤浅算法的写入屏障比较简单,所以保留了很多对象,无意间也保留了很多垃圾对象。
比较各个写入屏障
作者 | A | B | C | 时机 | 动作 |
---|---|---|---|---|---|
Dijkstra | 白 | 从a到b | 将C涂成灰色 | ||
Steele | 黑 | 白或灰色 | 从a到b | 将A恢复为灰色 | |
汤浅 | 白 | 从b到c | 将C涂成灰色 |
Incermental GC的更多相关文章
- 46张PPT讲述JVM体系结构、GC算法和调优
本PPT从JVM体系结构概述.GC算法.Hotspot内存管理.Hotspot垃圾回收器.调优和监控工具六大方面进行讲述.(内嵌iframe,建议使用电脑浏览) 好东西当然要分享,PPT已上传可供下载 ...
- C++随笔:.NET CoreCLR之GC探索(4)
今天继续来 带大家讲解CoreCLR之GC,首先我们继续看这个GCSample,这篇文章是上一篇文章的继续,如果有不清楚的,还请翻到我写的上一篇随笔.下面我们继续: // Initialize fre ...
- C++随笔:.NET CoreCLR之GC探索(3)
有几天没写GC相关的文章了哈,今天我讲GC的方式是通过一个小的Sample来讲解,这个小的示例代码只有全部Build成功了才会有.地址为D:\coreclr2\coreclr\bin\obj\Wind ...
- C++随笔:.NET CoreCLR之GC探索(2)
首先谢谢 @dudu 和 @张善友 这2位大神能订阅我,本来在写这个系列以前,我一直对写一些核心而且底层的知识持怀疑态度,我为什么持怀疑态度呢?因为一般写高层语言的人99%都不会碰底层,其实说句实话, ...
- C++随笔:.NET CoreCLR之GC探索(1)
一直是.NET程序员,但是.NET的核心其实还是C++,所以我准备花 一点时间来研究CoreCLR和CoreFX.希望这个系列的文章能给大家带来 帮助. GC的代码有很多很多,而且结构层次对于一个初学 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- Mono 3.2.7发布,JIT和GC进一步改进
Mono 3.2.7已经发布,带来了很多新特性,如改进的JIT.新的面向LINQ的解释器以及使用了64位原生指令等等. 这是一次主要特性发布,累积了大约5个月的开发工作.看上去大部分改进都是底层的性能 ...
- 深入浅出聊优化:从Draw Calls到GC
前言: 刚开始写这篇文章的时候选了一个很土的题目...<Unity3D优化全解析>.因为这是一篇临时起意才写的文章,而且陈述的都是既有的事实,因而给自己“文(dou)学(bi)”加工留下的 ...
- 不得不知的CLR中的GC
引言 GC 作为CLR的垃圾回收器,让程序员可以把更多的关注度放在业务上而不是垃圾回收(内存回收)上.其实很多语言也有类似的东东, 如Java也有JIT 等等 GC基本概念 垃圾回收机制的算法有好多种 ...
随机推荐
- 51nod 1272 最大距离 O(nlog(n)) , 快排 , 最大连续子串
题目: 解法:排序,把值小的和索引小的放在前面,记录一下之前索引最小的就可以了. 没什么可以讲的,上代码吧: #include <bits\stdc++.h> using namespac ...
- 安装Windows服务方法
用sc create 服务名 binPath="路径",不要用老方法InstallUtil会出现一堆的错误
- rem 使用
html{ font-size:12px; } .btn { width: 6rem; height: 3rem; line-height: 3rem; font-size: 2rem; displa ...
- div控制最小高度又自适高度
div控制最小高度又自适高度我们在用div布局的时候经常会遇到这样的一种情况:我们需要设置一个div的高度,当里面的东西超过这个高度时,让这个容器自动被撑开,也就是自适应高度.当里面的信息很少时候,我 ...
- 基本数据类型(list、tuple)
1.列表 1.1 定义 li=[1,2,3] 每个元素逗号隔开 list("abc") 迭代 列表是一个容器 => 任意类型 列表是有序的 => 索引 切片 步长 列表 ...
- HDU-1052 Tian Ji -- The Horse Racing 贪心 考虑特殊位置(首尾元素)的讨论
题目链接:https://cn.vjudge.net/problem/HDU-1052 题意 田忌赛马问题扩展版 给n匹马,马的能力可以相同 问得分最大多少 思路 贪心做得还是太少,一开始一点思虑都没 ...
- Adobe AIR and Flex - 保存序列化对象文件(译)
创建任何桌面应用程序几乎总是需要在本地存储数据,通过Adobe AIR我们有几下面几个选择,一个是我们能够使用内置的 SQLite 数据库支持,对于少量的数据这是大材小用了.另外一个选择是我们通过把数 ...
- HDU——T 1150 Machine Schedule
http://acm.hdu.edu.cn/showproblem.php?pid=1150 Time Limit: 2000/1000 MS (Java/Others) Memory Limi ...
- Cannot set web app root system property when WAR file is not expanded
Cannot set web app root system property when WAR file is not expanded 在tomcat下面可以,在weblogic下面不行的处理方法 ...
- Android里使用正則表達式
在Android里怎样使用正則表達式: 以验证username为例.username一般字母开头,同意字母数字下划线.5-16个字节: String regEx = "^[a-zA-Z][a ...