Java 8 内存管理原理解析及内存故障排查实践( 五 )

  • 重新标记阶段,这个阶段暂停用户线程,修正一些漏标对象,回扫发生引用变化的对象 。
  • 并发清理阶段, 这个阶段与用户线程一起执行,标记清除已经成为垃圾的对象 。
  • 三色标记
    • 黑色:代表了自己已经被扫描完毕,并且自己的引用对象也已经确定完毕 。
    • 灰色:代表自己已经被扫描完毕了,但是自己的引用还没标记完 。
    • 白色:则代表还没有被扫描过 。
    标记过程结束后,所有未被标记的对象都是不可达的,可以被回收 。
    Java 8 内存管理原理解析及内存故障排查实践

    文章插图
    三色标记算法的问题场景:当业务线程做了对象引用变更,会发生B对象不会被扫描,当成垃圾回收 。
    public class Demo3 {public static void mAIn(String[] args) {R r = new R();r.a = new A();B b = new B();// GCroot遍历R,R为黑色,R下面的a引用链还未扫完置灰灰色 , R.b无引用 ,  切换时间分片r.a.b = b;// 业务线程发生了引用改变 ,  原本r.a.b的引用置为nullr.a.b = null;// GC线程回来继续上次扫描,发现r.a.b无引用,则认为b对象无任何引用清除r.b = b;// GC 回收了b,业务线程无法使用b}} class R {A a;B b;} class A {B b;} class B {}
    Java 8 内存管理原理解析及内存故障排查实践

    文章插图
    当GC线程标记A时,CPU时间片切换,业务线程进行了对象引用改变 , 这时候时间片回到了GC线程 , 继续扫描对象A, 发现A没有任何引用,则会将A赋值黑色扫描完毕,这样B则不会被扫描,会标记B是垃圾, 在清理阶段将B回收掉,错误的回收正常的对象,发生业务异常 。
    CMS基于这种错误标记的解决方案是采取写屏障 + 增量更新Incremental Update ,在业务线程发生对象变化时,重新将R标识为灰色,重新扫描一遍,Incremental Update 在特殊场景下还是会产生漏标 。
    Java 8 内存管理原理解析及内存故障排查实践

    文章插图
     
    public class Demo3 {public static void main(String[] args) {// Incremental Update还会产生的问题R r = new R();A a = new A();A b = new A();r.a1 = a;// GC线程切换,r扫完a1 ,  但是没有扫完a2 ,  还是灰色r.a2 = b;// 业务线程发生引用切换, r置灰灰色(本身灰色)r.a1 = b;// GC线程继续扫完a2,R为黑色,b对象又漏了~}} class R {A a1;A a2;} class A {}当GC 1线程正在标记O,已经标记完O的属性 O.1,准备标记O.2时,业务线程把属性O,1 = B,这时候将O对象再次标记成灰色,GC 1线程切回,将O.2线程标记完成,这时候认为O已经全部标记完成 , O标记为黑色,B对象产生了漏标,CMS针对Incremental Update产生的问题,只能在remark阶段 , 暂停所有线程 , 将这些发生过引用改变过的,重新扫描一遍 。
    使用场景:
    【Java 8 内存管理原理解析及内存故障排查实践】适用于互联网或者 B/S服务, 响应速度优先,适合6G左右 。
    优点:
    并发收集,低停顿,回收过程中最耗时的是并发标记和并发清除 , 它都能与用户线程保持一起工作 。
    缺点:
    收集器对CPU的资源非常敏感,会占用用户线程部分使用,导致程序会变得缓慢,吞吐量下降 。
    无法处理浮动垃圾,在并发清理阶段用户线程还是在运行,这时候产生的新垃圾无法在这次当中处理,只有等待下次才会清理 。
    因为CMS使用了Incremental Update,remark阶段还是会所有暂停,重新扫描发生引用改变的GC root,效率慢耗时高 。
    因为收集器是基于标记清除算法实现的,所以在收集器回收结束后,内存会产生碎片化 , 当碎片化非常严重的时候 , 这时候有大对象进入无法分配内存时会触发FullGC , 特殊场景下会使用Serial收集器,导致停顿不可控 。
    (5)G1垃圾收集器
    G1也是采用三色标记分段式进行回收的算法,不过它是写屏障 + STAB快照实现,G1设定的目标是在延迟可控(低暂停)的情况下获得尽可能高的吞吐量,仍然可以通过并发的方式让Java 程序继续运行,G1垃圾收集器在很多方面弥补了CMS的不足,比如CMS使用的是mark-sweep标记清除算法,自然会产生内存碎片(CMS只能在Full GC时,STW 整理内存碎片),然而G1整体来看是基于标记整理算法实现的收集器,但是从局部来看也是基于复制算法实现的,高效的整理剩余内存,而不需要管理内存碎片它 。


    推荐阅读