ParallelScavenge + Parallel Old

Crystal_dan

ParalletScavenge:复制算法,多线程,关注吞吐率

Copying GC(GC复制算法):

最早是Robert R.Fenichel和Jerome C.Yochelson提出,简单说将空间分为From和To,当From空间完全占满时,gc将活动对象全部复制到To空间,复制完成后,From和To的使命互换。

为了保证From中所有活着的对象都能复制到To里,要求From和To空间大小一致。

coping(){
  free = to_start;
  for(r : roots){
    *r = copy(*r)
  }
  swap(from_start,to_start)
}
/**
*free设在To空间的开头
*第四行copy函数,复制能从根引用到的对象及其子对象,返回*r所在的新空间。
*然后交换From和To,对From空间进行gc
*/
 
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 = copy(*child)
    }
  }
  return obj.forwarding
}
/**
*obj.tag 并不是单独的一个域,只是占用了obj的field1,一旦obj被复制完毕,其在From空间的域值更改没有关系
*obj.forwarding也不是一个单独的域,占用了obj.field2,用来标识新空间的位置
* 由更上可知,需要每个对象至少有两个域
* 深度遍历
*/

image.png

//分配
new_obj(size){
  if(free + size > from_start + HEAP_SIZE /2){
    copying(); //空间不足,gc
    if(free + size > from_start + HEAP /2){
       allocation_fail();//还是不够,分配失败
    }
  }
  obj = free;
  obj.size = size;
  free += size;
  return obj;
}

copying算法的总结:

1 copying算法的时间复杂度只跟活跃对象的个数有关,可以在较短时间内完成gc,吞吐量较高

2 分配速度快,移动指针,不涉及空闲链表

3 不会产生碎片,复制的行为本身包含了压缩

4 深度优先,具有引用关系的对象靠在一起。利于缓存的使用(局部性原理)。

5 堆的利用率低

6  递归的进栈出栈消耗比迭代的形式大,但用迭代的形式是广度优先,jdk以前是广度,现在都是深度;有进阶算法可以近似接近深度优先而不需要递归

 Parallel Old:标记-整理,多线程

Mark Compact GC 标记-压缩算法:

首先看Donald E.Knuth研究出来的Lisp2算法:

compaction_phase(){
  set_forwarding_ptr();
  adjust_ptr();
  move_obj();
}
 
set_forwarding_ptr(){
  scan = new_address = heap_start;
  while(scan < heap_end){  //第一次搜索堆
    if(scan.mark == TRUE){  //被标记为活对象
      scan.forwarding = new_address;//new_address指向移动后的地址
      new_address += scan.size;
    }
    scan += scan.size; //扫描下一个活对象
  }
}
 
adjust_ptr(){
  for(r : roots){
    *r = (*r).forwarding;
  }
  
  scan = heap_start;
  while(scan < heap_end){  //第二次搜索堆
    if(scan.mark = TRUE){
      for(child : children(scan))
        *child = (*child).forwarding
    }
    scan += scan.size;
  }
}
 
move_obj(){
  scan = free = heap_start;
  while(scan < heap_end){//第三次搜索堆
     if(scan.mark = TRUE){
        new_address = scan.forwarding;
        copy_data(new_address,scan,scan.size);
        new_address.forwarding = null;
        new_address.mark = FALSE:
        free += new_address.size;
        scan += scan.size;
     }
  }
}

image.png

总结:

1 堆利用率高

2 压缩空间,没有碎片

3 时间消耗大:需要搜索三次堆,且时间与堆大小成正比

还有一个Two-Finger算法,可以做到只搜索两次,但要求所有对象整理成大小一致,这个约束比较苛刻,jvm用的应该是Lisp2。

分代垃圾回收:

可行性来源于经验:“大部分的对象在生成后很快就变成了垃圾,很少有对象能活得很久”

有些GC算法的时间是和活着的对象的个数成正比,比如标记-清除MarkSweep,复制Copying;

minorGC:新生代GC,minor指小规模的;

majorGC:老年代GC

promotion:新生代对象经过若干次gc仍活着的晋升为老年代。

Ungar的分代垃圾回收:David Ungar研究出来的将copying算法与分代思想结合

 1  堆划分为4个空间:生成空间new_start,2个大小相等的幸存空间survivor1_start,survivor2_start,老年代空间old_start

    2个幸存空间类似于copying gc中的From和To,每次利用生成空间+1个幸存空间,gc后活着的对象复制到另一个幸存空间

2  考虑:新生代gc:需要考虑老年代空间对新生代空间中对象的引用

Remembered Set & Card Table

image.png
Remembered Set:实现部分垃圾收集时(partial gc),用于记录从非收集部分指向收集部分的指针的集合的 抽象数据结构了。

在分代垃圾回收里,Remembered Set 用来记录老年代对象 对  新生代对象 的跨代引用;在regional collector中,Remembered Set 用来记录跨region的指针。

粒度;数据结构;写屏障

3 对象的晋升

 MaxTenuringThreshold 定义了晋升老年代的对象的最大年龄,大于这个年龄,一定会晋升,小于这个年龄,也有可能晋升,取决于TargetSurvivorRatio。

当年龄为age的对象的大小之和超过了targetSurvivorratio * survivor,则实际用来判断是否晋升的年龄是这个动态的age,不是maxtenuringthreshold

阅读 209
34 声望
0 粉丝
0 条评论
34 声望
0 粉丝
宣传栏