Copying GC (Part two :Multi Space Copying GC)

目录

  • 近似深度优先搜索方法

    • Cheney的GC复制算法
    • 前提
    • 执行结果
  • 多空间复制算法
    • multi_space_copying()函数
    • mark_or_copy()
    • copy()
    • 执行过程
    • 优缺点

近似深度优先搜索方法

Paul R.Wilson、Michael S.Lam、Thomas G.Moher,1991

这个方法只是近似深度优先搜索,但可以做到深度优先执行GC复制算法。

Cheney的GC复制算法

假设所有对象都是2个字,下图所示是对象间的引用关系。

下图所示是执行该算法时候,各个对象所在的页面(页面,在操作系统,和计算机组成原理课程中都有)。

右上角数字是页面编号,假如说页面容量是6个字(只能放3个对象)。

从上图不难看出,A,B,C是相邻的,这就是比较理想的状态。对于其他对象来说,降低了连续读取的可能性,降低了缓存命中率。

在下面1-4页中,同一个页面的对象甚至都没有引用关系(页面1中D和页面2中HI,有引用关系,但是不命中,需要读内存数据到catch),这样就不得不从内存上再去读。一直这样下去可想而知,有很多的对象会是这样的分布状态。

前提

在这个方法中有下面四个变量。

  • $page: 将堆分割成一个个页面的数组。$page[i]指向第i个页面的开头。
  • $local_scan:将每个页面中搜索用的指针作为元素的数组。$local_scan[i]指向第i个页面中下一个应该搜索的位置。
  • $major_scan:指向搜索尚未完成的页面开头的指针。
  • $free:指向分块开头的指针。

先复制A到To空间,然后复制他们的孩子B,C,都被放置到了0页。如下图示:

  • 因为A已经搜索完毕,所以$local_scan[0]指向B。
  • $free指向第一页的开头,也就是说下一次复制对象会被安排在新的页面。在这种情况下,程序会从$major_scan引用的页面和$local_scan开始搜索。
  • 当对象被复制到新页面时,程序会根据这个页面的$local_scan进行搜索,直到新页面对象被完全占满为止。
  • 此时因为$major_scan还指向第0页,所以还是从$local_scan[0]开始搜索,也就是说要搜索B。

  • 复制了D(B引用的对象),放到了$page[1]开头。像这样的页面放在开头时候,程序会使用该页面的$local_scan进行搜索。此时$local_scan[0]暂停,$local_scan[1]开始。之后复制了H,I。

  • 这里第一页满了,所以$free指向第二页开头。因此$local_scan[1]暂停搜索,程序$local_scan[0]开始搜索。(即对B对象再次进行搜索,看有没有其他孩子。)

  • 可以看到B的孩子E被复制到了$page[2],同样,对$local_scan[0]再次进行暂停,对E用local_scan[2]进行搜索。
  • 因此复制了J,K。

  • 通过对J,K的搜索页面2满了,$free指向了页面3。再次回到$local_scan[0]进行搜索。
  • 搜索完对象C,复制完A到O的所有对象之后状态如下图所示。

这样就搜索完了第0页($major_scan),虽然还没有搜索完子对象,但是孩子没有孩子,所以现在这个状态,和搜索完后是一样的。

执行结果

该方法是如何安排对象的呢?如下图示:

很明显能看出与Cheney的复制算法不同,不管下一个页面在哪里,对象之间都存在引用关系。

该方法,采用了不完整的广度优先,它实际上是用到了暂停的。从一开始我们就根据关系,然后进行暂停,将有关系的对象安排到了一个页面中。

多空间复制算法

GC复制算法最大的缺点就是只能利用半个堆。

但是如果我们把空间分成十份,To空间只占一份那么这个负担就站到了整体的1/10。剩下的8份是空的,在这里执行GC标记清除算法。

多空间复制算法,实际上就是把空间分成N份,对其中两份进行GC复制算法,对其中(N-2)份进行GC标记-清除

multi_space_copying()函数

muti_space_copying(){
    $free = $heap[$to_space_index]
    for(r :$roots)
        *r = mark_or_copy(*r)

    for(index :0..(N-1))
        if(is_copying_index(index) == FALSE)
            sweep_block(index)

    $to_space_index = $from_space_index
    $from_space_index = ($from_space_index +1) % N
}

将堆分为N等份,分别是$heap[0],$heap[1]...$heap[N-1]。这里的$heap[$to_space_index]表示To空间,每次执行GC时,To空间都会像$heap[0],$heap[1]...$heap[N-1],$heap[0],这样进行替换。Form空间在To空间的右边,也就是$heap[1]...$heap[N-1]。

  • 其中第一个for循环,为活动对象打上标记。能看出来是标记清除算法中的一个阶段。
  • 其中第一个for循环,当对象在From空间时,mark_or_copy()函数会将其复制到To空间,返回复制完毕的对象。如果obj在除Form空间以外的其他地方mark_or_copy()会给其打上标记,递归标记或复制它的子对象。
  • 其中第二个for循环,是清除阶段。对除From和To空间外的其他空间,把没有标记的对象连接到空闲链表。
  • 最后将To和From空间向右以一个位置,GC就结束了。

mark_or_copy()

mark_or_copy(obj){
    if(is_pointer_to_from_space(obj) == True)
        return copy(obj)
    else
        if(obj.mark == FALSE)
            obj.mark == TRUE
            for(child :children(obj))
                *child = mark_or_copy(*child)
        return obj

}

调查参数obj是否在From空间里。如果在From空间里,那么它就是GC复制算法的对象。这时就通过copy()函数复制obj,返回新空间的地址。

如果obj不在From空间里,它就是GC标记-清除算法的对象。这时要设置标志位,对其子对象递归调用mark_or_copy()函数。最后不要忘了返回obj。

copy()

copy(obj){
    if(obj.tag != COPIED)
        copy_data($free, obj, obj.size)
        obj.tag = COPIED
        obj.forwarding = $free
        $free += obj.size
        for(child :children(obj.forwarding))
            *child = mark_or_copy(*child)
        return obj.forwarding
}

递归调用不是copy()函数,而是调用mark_ or_copy()函数。如果对象*child是复制对象,则通过mark_or_copy() 函数再次调用这个copy()函数。

执行过程

将内存分为4等份。如下图示:

To空间$heap[0]空着,其他三个都被占用。这个状态下,GC就会变为如下如示:

我们将$heap[0]作为To空间,将$heap[1]作为From空间执行GC复制算法。此外$heap[2]和$heap[3]中执行GC标记-清除算法,将分块连接到空闲链表。

当mutator申请分块时候,程序会从空闲链表或者$heap[0]中分割出块给mutator。

接下来,To空间和From空间都向后移动一个位置。mutator重新开始。

这次$heap[1]是To空间,$heap[2]From空。这种状态下执行就会变为下图所示:

$heap[2]的活动对象都被复制到了$heap[1]中,在$heap[0]和$heap[3]中执行GC标记清除。然后From和To后移一次。

优缺点

优点

提高内存利用率:没有将内存空间二等分,而是分割了更多空间。

缺点

GC标记清除,分配耗时,分块碎片化。当GC标记清除算法的空间越小的时候,该问题表现的越不突出。例如将内存分为3份的情况下。

原文地址:https://www.cnblogs.com/Leon-The-Professional/p/9992345.html

时间: 2024-10-11 10:20:55

Copying GC (Part two :Multi Space Copying GC)的相关文章

深入JVM系列(二)之GC机制、收集器与GC调优(转)

一.回顾JVM内存分配 需要了解更多内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配2.大对象直接进入老年代 3.长期存活的对象将进入老年代 4.适龄对象也可能进入老年代:动态对象年龄判断 动态对象年龄判断: 虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才能晋升到老年代,当Survivor空间的相同年龄的所有对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代

深入JVM系列(二)之GC机制、收集器与GC调优

一.回想JVM内存分配 须要了解很多其它内存模式与内存分配的,请看 深入JVM系列(一)之内存模型与内存分配 1.1.内存分配: 1.对象优先在EDEN分配 2.大对象直接进入老年代 3.长期存活的对象将进入老年代 4.适龄对象也可能进入老年代:动态对象年龄推断 动态对象年龄推断: 虚拟机并不总是要求对象的年龄必须达到MaxTenuringThreshold才干晋升到老年代,当Survivor空间的同样年龄的全部对象大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就能够直接进入

源码分析HotSpot GC过程(二):DefNewGeneration的GC过程

由于虚拟机的分代实现,虚拟机不会考虑各个内存代如何实现垃圾回收,具体的工作(对象内存的分配也是一样)由各内存代根据垃圾回收策略自行实现. DefNewGeneration的使用复制算法进行回收.复制算法的思想是将eden和from区活跃的对象复制到to区,并清空eden区和from区,如果to区满了,那么部分对象将会被晋升移动到老年代,随后交换from和to区,即原来的to区存放了存活的对象作为新的from区存在,而from区被清空后当做新的to区而存在,移动次数超过一定阈值的对象也会被移动到老

java gc的工作原理、如何优化GC的性能、如何和GC进行有效的交互

java gc的工作原理.如何优化GC的性能.如何和GC进行有效的交互 一个优秀的Java 程序员必须了解GC 的工作原理.如何优化GC的性能.如何和GC进行有效的交互,因为有一些应用程序对性能要求较高,例如嵌入式系统.实时系统等.只有全面提升内存的管理效 率,才能提高整个应用程序的性能. 本篇文章首先简单介绍GC的工作原理,然后再对GC的几个关键问题进行深入探讨,最后提出一些Java程序设计建议,从GC角度提高Java程序的性能. GC的基本原理     Java 的内存管理实际上就是对象的管

OutOfMemoryError: Java heap space和GC overhead limit exceeded在Ant的Build.xml中的通用解决方案

这个只是一点点经验,总结一下,其中前两个对应第一个Error,后两个对应第二个Error,如果heap space还不够,可以再改大些. <jvmarg value="-Xms512m"/> <jvmarg value="-Xmx512m"/> <jvmarg value="-XX:-UseGCOverheadLimit"/> <jvmarg value="-XX:+UseConcMarkSwe

JVM(3)对象A和B循环引用,最后会不会不被GC回收?-------关于Java的GC机制

①首先说一下,GC里边在JVM其中是使用的ROOT算法,ROOT算法,什么称作为ROOT呢,就是说类的静态成员,静态成员就是static修饰的那种,是"根"的一个,根还包含方法中的成员变量.仅仅有成员或对象不挂在根上,GC的时候就可能把他们搞掉,这里提到的循环引用,就看这个循环引用是否挂在根上,假设挂在根上.假设这个根还被JVM的Java代码所运行的话,就不会GC掉,假设说这个根已经被释放掉了.这个对象不挂在跟上了.那个这个对象就会被GC掉. ②说一下根搜索算法,ROOTS,这个算法,

对象A和B循环引用,最后会不会不被GC回收?-------关于Java的GC机制

①首先说一下,GC里边在JVM当中是使用的ROOT算法,ROOT算法,什么称作为ROOT呢,就是说类的静态成员,静态成员就是static修饰的那种,是"根"的一个,根还包括方法中的成员变量,只有成员或对象不挂在根上,GC的时候就可能把他们搞掉,这里提到的循环引用,就看这个循环引用是否挂在根上,如果挂在根上,如果这个根还被JVM的Java代码所执行的话,就不会GC掉,如果说这个根已经被释放掉了,这个对象不挂在跟上了,那个这个对象就会被GC掉. ②说一下根搜索算法,ROOTS,这个算法,那

【Java面试题】48 GC是什么? 为什么要有GC?

GC是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java提供的GC功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java语言没有提供释放已分配内存的显示操作方法. GC是垃圾收集器.Java 程序员不用担心内存管理,因为垃圾收集器会自动进行管理.要请求垃圾收集,可以调用下面的方法之一: System.gc() Runtime.getRuntime().gc()  Java是由

GC标记-清除算法(Mark Sweep GC)

世界上第一个GC算法,由 JohnMcCarthy 在1960年发布. 标记-清除算法由标记阶段和清除阶段构成. 标记阶段就是把所有的活动对象都做上标记的阶段. 标记阶段就是"遍历对象并标记"的处理过程. 标记阶段经常用到深度优先搜索. 清除阶段就是把那些没有标记的对象,也就是非活动对象回收的阶段. 清除阶段collector会遍历整个堆,回收没有打上标记的对象(即垃圾). 内存的合并操作也是在清除阶段进行的. 分配 分配指将回收的内存空间进行再利用. -> 伪代码实现内存分配