1.Parallel Scavenge+Parallel Old
Parallel Scavenge负责年轻代垃圾回收,采用复制算法,多线程并行回收。
Parallel Old负责老年代垃圾回收,采用标记-整理算法,多线程并发收集,暂停时间会比较长。
2.ParNew+CMS
ParNew负责年轻代垃圾回收,采用复制算法,多线程并行回收。
CMS负责老年代垃圾回收,采用标记-清除算法,停顿时间较短。但是会存在内存碎片问题
、退化问题、CPU资源敏感问题和浮动垃圾问题。
CMS垃圾回收过程:
总共分为四步:
1.初始标记:stop the world,标记和GC root直接关联的对象。
2.并发标记:用户线程和标记线程同时进行,标记所有对象。
3.重新标记:stop the world,标记上一个并发步骤中产生的新垃圾。
4.并发清除:用户线程和垃圾回收线程同时进行,清除已经标记的垃圾。
CMS产生的三个问题:
1.内存碎片:因为CMS采用的是标记-清除算法,所以会产生内存碎片。
2.浮动垃圾:因为在第4步并发清除阶段也会有新的垃圾出现,这类垃圾就叫浮动垃圾,需要等到下一次的垃圾回收过程中才能被清除。
3.容易退化成Serial Old垃圾回收器:当创建一个新的大的对象需要被放到老年代,但是老年代内存不够的时候,会退化成Serial Old垃圾回收器重新来对老年代的垃圾收集。
4.CPU资源敏感:CMS 中并发阶段运行时的默认线程数=(CPU的数量 + 3) / 4,所以CPU数量少会导致程序变慢。
G1垃圾回收器:
特点:将整个堆划分成多个大小相等的区域,叫Region。每个Region可以是Eden 、Survivor 、或者Old 区。Region size必须是2的指数幂,取值范围从1M到32M。
垃圾回收方式:
1.年轻代回收:回收Eden 区和Survivor 区中不用的对象,采用复制算法。可以设置每次垃圾回收时的最大暂停时间毫秒数。
注意:
1.新创建的对象会存放在Eden 区。当G1判断年轻代占总堆的百分之六十以上时,无法分配对象时需要回收时会执行Young GC 。
2.标记出Eden 和Survivor 区域中的存活对象,
3.根据配置的最大暂停时间选择某些区域将存活对象复制到一个新的Survivor 区中(年龄+1 1),清空这些区域。
当对象年龄达到15,将被放到老年代。
如果对象超出Region的一半,那么会被直接放到老年代中,这种老年代叫Humongous区,这种老年代可以连续占用多个Region。
2.混合回收:
注意:
当总堆占有率达到阈值时默认45%时会触发混合回收MixedGC 。回收所有年轻代和部分老年代的对象以及大对象区,采用复制算法来完成,如果发现没有空的Region来存放,就会FULL GC,会单线程执行标记-整理算法。
G1解决跨代引用的方法:
加入老年代引用了Eden区的对象,且这个Eden区的对象没有被其他Eden区的对象引用,就出现了跨代引用的问题。因为Young GC只会扫描年轻代里的所有对象,因此这个对象会被当做垃圾清理掉,导致老年代出现空指针异常。
解决方案:使用卡表。
卡表本质上就是一个字节数组,每一个Region都有一个卡表,卡表数组的每一个下标代表对应一个512字节的区域。如果该区域存在跨代引用,卡表对应位置的数值设为0(dirty card),并把它加入到记忆集中。当Young GC时,会把记忆集对应区域的对象加入到GC root对象中进行扫描。
当老年代引用年轻代对象时会使用卡表,那么G1如何实现这个功能?
使用写屏障:在建立引用关系时,利用写屏障将跨代应用记录到卡表中。
记忆集记录跨代引用整体流程:
1.通过写屏障指令将跨代引用记录到卡表。
2.将记录写到脏卡队列中。
3. 通过Refinement线程消费脏卡队列,进行记忆集的更新。
G1垃圾回收器年轻代回收的完整流程:
1.Root扫描,获取GC Root。
2.更新完成最终记忆集。
3.标记存活对象,将记忆集内的对象也加入到GC Root中。
4.设置最大存活时间,选择收集区域。
5.复制算法清除对象。存活对象年龄加1,如果到达15,晋升老年代。
6.处理软、弱、虚、终结器引用。将已经回收的对象加入到对应的引用队列。
G1垃圾回收器混合回收的完整流程:
总共分为四步:
第一步.初始标记:stop the world,采用三色标记算法标记GC Root直达的对象。
三色标记算法中黑色代表当前对象和子对象已经被扫描、灰色带表当前对象已经被扫描、白色代表当前对象还没有被扫描。
第二步.并发标记:用户线程和标记线程同时进行,沿着第一步的对象继续标记所有对象。
存在问题:并发标记的过程中,用户线程修改了引用关系导致错标的情况。
解决方案:
SATB技术(初始快照技术)过程:
1.标记开始先创建一个快照,记录所有对象,标记过程中新生成的对象直接标记为黑色。
2.采用写屏障技术,在用户改变引用关系的语句之前进行判断,引用的对象是否在初始快照当中?如果在的话就将这个对象放到这个线程的SATB待处理队列中。最终将多个线程的SATB待处理队列合并成一个大的SATB处理队列。
第三步.最终标记:stop the world,将SATB队列中的对象默认存活,同时处理它们引用的对象。
这一步也存在缺点:就是会产生浮动垃圾,将这一轮已经是垃圾的对象标记成了存活,需要等到下一次垃圾清理再清理掉。
第四步.清除:stop the world,如果区域内没有任何存活对象就直接清理掉。
第五步.转移:最终根据最终标记的结果,计算出每一个区域的垃圾占用内存的大小,根据设置的停顿时间选择转移效率最高的几个区域,采用复制算法转移。如果Region不够了,则进行单线程的FULL GC,使用标记-整理算法。
其中G1对老年代的清理会选择存活度最低的区域来进行回收。