垃圾回收


一、可达性分析

GC ROOT

虚拟机栈中的引用的对象

方法区中静态属性引用的对象

方法区中常量引用的对象

本地方法栈中引用的对象

虚拟机内部的引用,比如基本数据类型对应的Class对象、常驻的异常对象(NullPointException、OutOfMemoryError等)、系统类加载器

GC Roots的生成

1)在类加载完成后,记录对象什么偏移量上是什么类型的数据
2)在安全点,记录下栈和寄存器中哪些位置是引用

安全点

按照是否具有让程序长时间执行的特征为标准选取安全点。如指令序列的复用(方法调用、循环跳转、异常跳转等)特征

通过主动轮训标志位(test指令)的内存保护陷阱方式。

安全点包含轮训标志的地方及创建对象和其它需要在Java堆上分配内存的地方(检查是否将要发生垃圾回收,避免没有足够的内存分配)

安全区域

确保在某一代码片段中,引用关系不会发生变化,因此在安全区域内进行垃圾收集是安全的。

当用户线程进入安全区域后,首先标识自己进入到了安全区域,这样垃圾收集时今不用管这些进入安全区域的线程;当用户线程要离开安全区域时,要检查根节点枚举是否已经完成,完成则可以继续执行,否则等待,直到收到可以离开安全区域的信号。

记忆集(Remebered Set)

记录从非收集区域指向收集区域的指针集合的抽象数据结构。

卡表实现:

卡槽为512个字节,记录非收集区域对象的512字节内存区域是否指向收集区域。

写屏障

虚拟机通过写屏障技术,在引用类型字段赋值动作发生时加入AOP切面实现。

为避免卡表元素的伪共享问题,可使用有条件的写屏障(即判断卡表元素不是Dirty状态是才会更新卡表)

void oop_field_store(oop* field, oop new_value) {  
    pre_write_barrier(field); // 写屏障-写前操作
    *field = new_value; 
    post_write_barrier(field, value);  // 写屏障-写后操作
}

增量更新时写后屏障,也就是引用变化后:

void post_write_barrier(oop* field, oop new_value) {  
  if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) {
      remark_set.add(new_value); // 记录新引用的对象
  }
}

SATB是写前屏障,也就是引用变化前:

void pre_write_barrier(oop* field) {
  // 处于GC并发标记阶段 且 该对象没有被标记(访问)过
  if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) { 
      oop old_value = *field; // 获取旧值
      remark_set.add(old_value); // 记录  原来的引用对象
  }
}

读屏障:

void pre_load_barrier(oop* field, oop old_value) {  
  if($gc_phase == GC_CONCURRENT_MARK && !isMarkd(field)) {
      oop old_value = *field;
      remark_set.add(old_value); // 记录读取到的对象
  }
}

三色标记

使用白灰黑三种颜色标记对象。白色是未标记;灰色自身被标记,引用的对象未标记;黑色自身与引用对象都已标记。

1)对象消失的充要条件:
赋值器删除了所有灰色对象指向该白色对象的直接或间接引用
赋值器添加一个或多个从黑色对象到白色对象的引用

2)对象消失的解决方案:

基于原始快照(SATB):破坏冲要条件的第一条,当删除灰色对象到白色对象的引用时,会将该应用关系记录下来;待并发标记结束后,重新扫描记录中的灰色对象(重新标记)。
可以理解为,无论删除与否,都是基于该开始那一刻的原始快照进行扫描

增量更新:破坏充要条件的第二条,当添加一个从黑色对象到白色对象的引用时,会将该引用关系记录下来;待并发标记结束时,重新扫描记录中的黑色对象(重新标记)。
可以理解为,当添加黑色对象到白色对象的引用时,将黑色对象标记为灰色。

CMS使用的是增量更新,G1使用的是原始快照。

二、回收算法

  • 复制

新生代:少量对象存活

  • 标记整理

老年代:大多数对象都是存活

标记清除

老年代:大多数对象都是存活

三、垃圾回收器

(一)、Serial

  • 新生代
  • 复制算法
  • 单线程

(二)、ParNew

  • 新生代
  • 复制算法
  • 多线程
  • 并行

(三)、Parallel Scavenge

吞吐量 = (业务代码执行时间)/ (业务代码执行时间+GC时间)

  • 新生代
  • 复制算法
  • 多线程
  • 并行
  • 更关注吞吐量

(四)、Serial Old

  • 老年代
  • 标记整理
  • 单线程

(五)、Parallel Old

  • 老年代
  • 标记整理
  • 多线程
  • 并行
  • 更关注吞吐量

(六)、CMS

  • 老年代
  • 标记清除
  • 多线程
  • 并发
  • 更关注停顿时间

初始标记

并发标记

重新标记

并发清理

(七)、GC

  • 尝试满足最小停顿时间(可以设置),同时拥有更高的吞吐量
  • 逻辑上的Eden、Survived、Old,大对象(H),物理上不分
  • 适用于堆内存使用超过50%、对象晋升率高、停顿时间要求高。

初始标记

并发标记

最终标记

筛选回收(堆每个region的排序选择性清理,以期达到期望)