MostlyCopyingGC

Mostly Copying GC, Joel F.Bartlett, 1989

此算法可以在不明确根的环境中运行GC复制算法。

概要

Mostly Copying GC就是“把不明确的根指向的对象以外的对象都进行复制”,抛开那些不能移动的对象将其他大部分的对象进行复制的GC算法。

  • 前提

    • 根是不明确的根
    • 没有不明确的数据结构 :可以明确判断对象里的域是指针还是非指针
    • 对象大小随意

堆结构

下图所示,是 Mostly Copying GC的堆结构,堆被分成一定大小的页(page),每个页都有编号。那些没有分配到对象的空页则有一个$current_space以外的编号。页编号不能造成数据溢出。

GC时,$current_space 和 $next_space用于识别To空间和From空间。编号和$next_space一样的是To,编号和$current_space一样的是From页面。一般情况下$current_space和$next_space是同一个值,只有在GC时才会不同。$current_space的值会被分配到装有对象的正在使用的页。如上图示,正在使用的页编号为1.

此外,我们要为正在使用的页设置一下两种标志的一种。

  • OBJECT : 正在使用的页
  • CONTINUED :当正在使用的页跨页时,设置在第二个页之后。

以上两个标识不是一块排列在内存当中,因为会出现跨页分配对象,所以从实现上来说,我们必须把页和标识分配在不同的内存位置进行管理。

分配

根据分块的大小、分配对象的大小不同,分配的动作也各不相同。

  • 如果正在使用的页里有符合mutator申请大小的分块,对象就会被分到这个页。如下图示:

  • 当正在使用的页没有合适大小的分块时,对象就会被分配到空的页,然后正在使用的这个新页会被设置OBJECT标识。

  • 当mutator申请分配超过页大小时。分配程序会将对象夸多个页来分配。和平时一样,开头页设定OBJECT,之后的页设定CONTINUED。如下图示:

new_obj()函数

分配的伪代码

new_obj(size){
while(size > $free_size) // $free_size用来保持分块大小
$free_size = 0
add_page(byte_to_page_num(size)) obj = $free
obj.size = size if(size < PAGE_SIZE)
$free_size -= size
$free += size
else
$free_size = 0 return obj
}
  • 将要申请的大小size自己传给new_obj。
  • 判断size和$free_size大小。如果$free_size小于size 那么add_page()函数会分配新的页,扩大分块大小。然后新分配的页数会传递给add_pages()函数。
  • $free指向分块开头, 将obj设置为$free
  • 判断size是否小于$PAGE_SIZE。当size小于页大小时,则会从$free_size中减去size。也就是当前页剩余的大小。然后指针$free向后移动size。
  • 如果size大于$PAGE_SIZE。$free_size变为0。这样一来对象就不会被分到CONTINUED页了。

add_pages()函数

负责重新分页的add_pages()函数。

add_pages(page_num){
if($allocated_page_num + page_num >= HEAP_PAGE_NUM/2)
mostly_copying()
return first_free_page = find_free_pages(page_num) if(first_free_page == NULL)
allocation_fail() if($next_space != $current_space)
enqueue(first_free_page, $to_space_queue) allocate_page(first_free_page, page_num)
}
  • $allocated_page_num 表示正在使用的页数,HEAP_PAGE_NUM表示堆中的总页数。如果出现“正在使用的页数+准备追加的页数>总页数的一半”,这种情况下启动GC mostly_copying()。
  • first_free_pages()函数,在堆内寻找连续的page_num个空页。如果有则返回最开头的空页指针,否则返回NULL表示失败。
  • 当运行GC复制对象时,为了使用new_obj() 函数,也会在GC里调用这个add_ pages() 函数。另外,第三个if的条件只有在 GC 里才为真。之后会把GC中分配的页连接上$to_space_queue。当GC执行时,这里连接上$to_space_queue的页相当于To空间。
  • 最后一行是对于找到连续page_num的指针调用allocate_pages()方法,申请空间。
allocate_pages(first_free_page, page_num){
$free_page = first_free_page
$free = first_free_page
$free_size = page_num*PAGE_SIZE
$allocated_page_num += page_num set_space_type(first_free_page, $next_space)
set_allocate_type(first_free_page, OBJECT) while(--page_num > 0)
$free_page = next_page($free_page)
set_space_type($free_page, $next_space)
set_allocate_type($free_page, CONTINUED)
$free_page = next_page($free_page) }
  • 其中set_space_type()函数将新的空页编号设置成$next_space的值。也就是说,只要在GC里,这个页就会当做To空间。set_allocate_type()给函数的页设置了OBJECT标识。
  • while循环用于分配页数大于等于2的时候有效。next_space()函数用来返回被用作参数的页的下一个页。

GC执行过程

下图标识GC执行前堆的状态。这时$current_space和$next_space的值是相同的。首先对$next_space进行增量。一旦GC开始执行,与$current_space值相同的页就是From页,与$next_space值相同的页就是To页。

之后我们将那些保留有从根引用的对象的页“晋升promotion”到To页。下图示:这里的晋升是指将页的编号设定为$next_space的值把它当做To空间处理。

因为对象A是根引用的,所以我们将该对象的页面编号设定为$next_space的值。也就是$next_space = 2

把所有从根引用的页都晋升后,下面就是把To页里的对象的子对象复制到空页了。这个时候对象Y(垃圾对象)引用的D也会被复制过去。然后空页的编号会被设定为$next_space。也就是说这个页变为了To页。

接下来,我们要把追加的To页里的对象的子对象复制到To页的分块里。如果To页里没有分块,那么对象就会被复制到空页,目标页的编号会被设定为$next_space。上图中To页有分块,所以直接复制对象E。如下图示:

当所有对象的子对象复制完毕后GC就结束了,此时$current_space的值设定为$next_space的值。如下图示:

从上图得知,垃圾对象X,Y,D都没有被回收。MostlyCopyingGC的特殊之处就是不会回收包含有从根指向的对象(A)所在页的垃圾对象,并且也不会回收这个垃圾对象所引用的对象群。极端一点,如果所有页里都有对象被根指着,代表所有垃圾不能被回收。

缺页可以通过调整也大小来改善。实验表明页大小适合在512字节。实际上自己在生产环境中那个好就是那个了。

mostly_copying()函数

该方法是用来执行GC的函数,由add_pages()调用。

mostly_copying(){
$free_size = 0 //为了不把对象复制到From空间里去,GC将From页里的分块大小设置为0
$allocated_page_num = 0 $next_space = ($current_space) %N // 将next_space进行增量。为了避免$next_space溢出,增量时必取常量N余数。 for(r :$roots) //保留根直接引用的对象所在页。
promote_page(obj_to_page(*r)) //obj_to_page函数将对象作为参数,返回保留的对的页。 while(is_empty($to_space_queue) == FALSE) //复制To页里的子对象。除去CONTINUED页,所有的To页都连接到了$to_space_queue。我们将其取出并传递给page_scan().
page_scan(dequeue($to_space_queue)) $current_space = $next_space }

MostlyCopyingGC不会特意把因GC变空的空页的编号置为0.因此空页的编号可能会很混乱,为此常量N的数值必须必空页的总数大得多,以保证及时给所有空页分配唯一的编号,程序也能识别编号被设为$next_space的页和其他的页。

promote_page()函数

是将用作参数的页晋升的函数。如果用作参数的页 里的对象跨了多个页,那么这些页都会被一起晋升。

promote_page(page){
if(is_page_to_heap(page) == True && space_type(page) == $current_space && allocate_type(page)== OBJECT)
promote_continued_page(next_page(page)) //下面有源码 // 将晋级的page连接到$to_space_queue
set_space_type(page, $next_space)
$allocated_page_num++
enqueue(page, $to_space_queue) }
  • 判断条件

    • 是否在堆内
    • 页编号是否和$current_space相同
    • 是否有OBJECT标识
promote_continued_page(page){
while(space_type(page) == $current_space && allocate_type(page) == CONTINUED) //调查用作参数的页编号是否为$current_space,以及是否设置了 CONTINUED 标志。
set_space_type(page, $next_space)
$allocated_page_num++
page = next_page(page)
}
  • while中调查用作参数的页编号是否为$current_space,以及是否设置了 CONTINUED 标志。如果为真,则参数的页里的对象夸了多个页,这是全部晋升。

对象不被分配到CONTINUED页,其原因就是这里的最后一行代码。如果分配到了CONTINUED页,那么对象就有可能跨页,此时CONTINUED页的下一个页会很有可能也是CONTINUED。如果重新放置到一个空页的话,它是没有下一页的。这就造成了原本不用也不想复制的对象由于在CONTINUED中所以也被复制了

page_scan()函数

把那些持有从根引用的对象的页全部晋升后,下面就要复制到To页里的对象的子对象。

page_scan()函数,是通过mostly_copying()函数调用的函数。这个函数只接受To页作为参数。

page_scan(to_page){
for(obj : objects_in_page(to_page))
for(child : children(obj))
*child = copy(*child)
}

这个函数被用于将页里所有对象的子对象都交给 copy() 函数,并把对象内的指针都改 写成目标空间的地址。

copy()函数

将复制对象用作参数。

copy(obj){
if(space_type(obj_to_page(obj)) == $next_space) //检查持有obj的页是否是To页。如果是就不会被复制直接返回对象。
return obj if(obj.field1 != COPIED) //检查对象是否复制完毕。
to = new_obj(obj.size) // 没有复制完毕,则使用该方法来分配空间
copy_data(to, obj, obj.size) //将对象复制。
obj.field1 = COPIED // 修改复制标记,表示已复制。
obj.field2 = to // 更改指针地址
return obj.field2 //返回对象地址(也就是目标空间的地址即原对象forwarding)
}

优缺点

优点:使用了GC复制算法,包含它的优点。

缺点:部分垃圾没有被回收。

黑名单

Hans J.Boehm 黑名单法

保守式GC的缺点之一,就是使用指针识别错误,本来要被删除的垃圾却被保留了下来,甚至造成其他更严重的错误。改善这个问题可采用Hans J.Boehm 发明的黑名单法。

指针的错误识别带来的害处

在指针的错误识别中,被错误判断为活动对象的那些垃圾对象的大小及内容至关重要。

  • 大小:有个巨大的对象死掉了,而保守式 GC 却把它错误识别成“它 还活着”,这样当然就会压迫到堆了。
  • 数量:。保守式 GC 会错误识别子对象的子对象,以及子对象的子对象的子对象,错误就会像多米诺骨牌一样连续下去。

黑名单

这个黑名单里记录 的是“不明确的根内的非指针,其指向的是有可能被分配对象的地址”。我们将这项记录操作称为“记入黑名单”。 可能被分配的对象的地址指的是堆内未使用的对象的地址

mutator无法引用至今未使用过的对象如果,根里存在有这种地址的指针,那它肯定就是“非指针”,就会被记入黑名单中。

们在GC标记-清除算法中的 mark() 函数里导入记入黑名单的操作,其伪代码如下。

mark(obj){
if($heap_start <= obj && obj <= $heap_end)
if(!is_used_object(obj))
obj.next = $blacklist
$blacklist = obj
else
if(obj.mark == FALSE)
obj.mark == TRUE
for(child :children(obj))
mark(*child)
}

如果对象正在使用,is_used_obj()就会返回真。在GC开始时候黑名单会被丢弃,也就是说,在标记阶段需要注意的地址会被记录在新的黑名单里

面向黑名单内存地址分配注意

黑名单里记录的是“需要注意的地址”也就是说这个对象就很可能被非指针值所引用。在将对象分配到需要注意的地址时,为所分配的对象设如下限制条件。

  • 没有小对象
  • 没有子对象少,或者说子对象的孩子加起来不多。

优缺点

优点:保守式 GC 因错误识别指针而压迫堆的问题得到缓解,堆使用效率提升,没有多余对象GC速度也会提升。

缺点:花费时间检测黑名单。

Conservative GC (Part two :MostlyCopyingGC )的更多相关文章

  1. 【转载】Java性能优化之JVM GC(垃圾回收机制)

    文章来源:https://zhuanlan.zhihu.com/p/25539690 Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我 ...

  2. Java性能优化之JVM GC(垃圾回收机制)

    Java的性能优化,整理出一篇文章,供以后温故知新. JVM GC(垃圾回收机制) 在学习Java GC 之前,我们需要记住一个单词:stop-the-world .它会在任何一种GC算法中发生.st ...

  3. Conservative GC (Part one)

    目录 保守式GC 不明确的根 指针和非指针的区别 貌似指针的非指针 不明确数据结构 优点 准确式GC 正确的根 打标签 不把寄存器和栈等当做根 优点 缺点 间接引用 经由句柄引用对象 优缺点 保守式G ...

  4. JVM的GC(概念与深入)

    深入浅出了解什么是GC: http://my.oschina.net/xianggao/blog/86985 GC策略详解: http://blog.csdn.net/winniepu/article ...

  5. Java中的内存泄露 和 JVM GC(垃圾回收机制)

    一.什么是Java中的内存泄露? 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点, 首先,这些对象是可达的,即在有向图中,存在通路可以与其相连:其次,这些对象是无用的,即程序以 ...

  6. java基础知识汇总(持续更新中....)

    1.java四大特性:抽象.继承.封装,多态 构造函数: http://blog.csdn.net/qq_33642117/article/details/51909346 2.java数据基本类型: ...

  7. JVM GC(整理)

    1 GC类型 1 )YGC  一般情况下,当新对象生成,并且在Eden申请空间失败时,就好触发YGC ,堆Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区.然后整理S ...

  8. 垃圾回收机制GC知识再总结兼谈如何用好GC(转)

    作者:Jeff Wong 出处:http://jeffwongishandsome.cnblogs.com/ 本文版权归作者和博客园共有,欢迎围观转载.转载时请您务必在文章明显位置给出原文链接,谢谢您 ...

  9. 深入理解JAVA虚拟机(内存模型+GC算法+JVM调优)

    目录 1.Java虚拟机内存模型 1.1 程序计数器 1.2 Java虚拟机栈 局部变量 1.3 本地方法栈 1.4 Java堆 1.5 方法区(永久区.元空间) 附图 2.JVM内存分配参数 2.1 ...

随机推荐

  1. Docker学习笔记(1)----认识Docker

    1. 什么Docker? Docker是一个能把开发的应用程序自动部署到容器的引擎,它使用go语言编写的开源引擎,它在github上面个的地址为:https://github.com/docker/d ...

  2. 解析数据(正则,xpath)

    正则表达式拆分 import re # 1.拆分字符串 one = 'asdsfsgsh' # 标准 是 s 为拆分 pattern = re.compile('s') result = patter ...

  3. sql知识小记

    1.在sql语句中,单引号嵌套时,使用单引号做转义

  4. pandas 4 处理缺失数据nan

    from __future__ import print_function import pandas as pd import numpy as np np.random.seed(1) dates ...

  5. 紫书 习题 11-2 UVa 1001 (Floyd)

    这道题只是在边上做一些文章. 这道题起点终点可以看成半径为0的洞, 我是直接加入了洞的数组. 边就是两点间的距离减去半径, 如果结果小于0的话, 距离就为0, 距离不能为负 然后我看到n只有100, ...

  6. hook中ref使用

    hook使用ref 父组件: 引入                  useRef 声明ref的名字     const dateRef = useRef() 复值给组件         ref={d ...

  7. [terry笔记]11gR2_dataguard_保护模式切换

    保护模式切换 Maximum protection/availability/ performance 1. 首先查看当前的保护模式 SQL> select protection_mode,pr ...

  8. Camera Calibration 相机标定:原理简介(一)

    1 相机标定常见方法 广义来说,相机标定不单包括成像过程的几何关系标定,还包括辐射关系的标定,本文只探讨几何关系.相机标定是3D计算机视觉(Computer Vision)里从2D图像中提取量测信息的 ...

  9. cocos2dx 使用spine制作骨骼动画

    刚刚接触骨骼动画,所以写一篇文章记录. 1.首先先画好人物的每一个部件: watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fon ...

  10. JAVA输出带BOM的UTF-8编码的文件

    当从http 的response输出CSV文件的时候,设置为utf8的时候默认是不带bom的,可是windows的Excel是使用bom来确认utf8编码的,全部须要把bom写到文件的开头. 微软在 ...