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. 优化长的switch语句

    突然想到之前碰到的一个优化的面试题,现在想想switch用的太傻 public enum FormatType { GetKey, GetValue } public class Format { p ...

  2. 为什么同样的数据,俩人生成的obj和bin文件不一样

    http://bbs.csdn.net/topics/270055083 编译器编译的时候可能有些东西依赖时间,或许是优化的原因,如果可以,换个编译器试试,或许两次编译的时候,强制把系统时间调成一个看 ...

  3. SpringBoot学习笔记(10)-----SpringBoot中使用Redis/Mongodb和缓存Ehcache缓存和redis缓存

    1. 使用Redis 在使用redis之前,首先要保证安装或有redis的服务器,接下就是引入redis依赖. pom.xml文件如下 <dependency> <groupId&g ...

  4. Matlab---从入门到精通 Chapter 4 编程基础

    ---恢复内容开始--- 4-1 M文件编辑器 在命令窗口输入edit命令,可以打开M文件编辑器,创建新的M文件 在命令行中输入edit filename,那么可以打开在当前目录环境下的M文件 4-2 ...

  5. [BZOJ3673&3674]可持久化并查集&加强版

    题目大意:让你实现一个可持久化的并查集(3674强制在线). 解题思路:刚刚介绍了一个叫rope的神器:我是刘邦,在这两题(实际上两题没什么区别)就派上用场了. 正解应该是主席树||可持久化平衡树,然 ...

  6. Python开发的简单记事本

    ---恢复内容开始---               主要是利用python 自带的tkinter 库    程序的基于python3.0以上 ,各个平台都可以使用包括linux ,windows , ...

  7. 紫书 习题8-14 UVa 1616(二分+小数化分数+精度)

    参考了https://www.cnblogs.com/dwtfukgv/p/5645446.html (1)直接二分答案.说实话我没有想到, 一开始以为是贪心, 以某种策略能得到最优解. 但是想了很久 ...

  8. Generator 简介

    Generator 就是一种状态机,封装多个内部状态. 执行 Generator 函数会返回一个遍历器对象(),也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数.返回的遍历器对 ...

  9. mysql5.7 安装方法 (跟旧的不一样了)

    MySQL 5.7发布之后很多网友都在说,打开想安装文件夹.但是文件夹中没有DATA目录, 没有mysqly默认库.启动不了数据库,那是因为5.7的数据库的初始化方法和之前的初始化不一样了. 首先这里 ...

  10. POJ——T 1422 Air Raid

    http://poj.org/problem?id=1422 Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 8579   A ...