1 GC的算法

  1. 引用计数法
  2. 标记清除
  3. 标价压缩
  4. 复制算法


    1.1引用计数法

     引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为0,则对象A就不可能再被使用。

    

Java 产看gc命令_算法

    但是很难解决循环引用的问题(如下图),而且每个对象都维护者一个计数,带来性能上的开销,所以java没有采用这种算法

  

Java 产看gc命令_Java 产看gc命令_02

   



    1.2 标记清除法

    标记清除是现代垃圾回收的思想基础。分为俩个阶段标记阶段和清除阶段。一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清除所有未被标记的对象。

注意:白色:空闲空间,浅灰色:使用的空间,深色:垃圾空间(下面的图例同这)



    1.3 标记压缩法

    同标记清除法一样有俩个阶段:标记,压缩阶段。有别于1.2方法,压缩阶段它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端,在清除边界外的所有空间。这种适用于需要复制对象比较少,存活对象比较多的时候,eg:老年代

Java 产看gc命令_java_03

    比较1.2和1.3俩种方式,1.3的主要优势是可以避免内存碎片。



    1.4复制算法

    将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

    相比于标记清除法,他是一个比较高效的方式,但是也比较浪费有限的内存空间,不适用于存活对象较多的场合 如老年代。

相比于标记清除法,他是一个比较高效的方式,但是也比较浪费有限的内存空间,不适用于存活对象较多的场合 如老年代



    1.5JVM的GC初识

    综上,整合上面的思想,JVM会依据对象的存活周期进行分类,短命对象归为新生代,长命对象归为老年代

据不同代的特点,选取合适的收集算法:少量对象存活,适合复制算法。大量对象存活,适合标记清理或者标记压缩。这也就分代回收的思想

以下是图例是方式。

Java 产看gc命令_jvm_04

    新生代采用标记压缩的方式,将小数据复制到survivor的空闲空间,大对象转移到老年代。同时survivor将对象也将对象复制到survivor空闲空间和老年代(老年作为担保空间起作用的时候)。然后清除俩块区域,GC完后就是上图右边的图。

打印PrintGCDetails可以看到如下信息,



2 GC的对象

    当一个对象不可达时候,对象就最终会被GC,那么问题来了,什么时候对象是可达的,什么时候是不可达的。我们来看看对象被回收前的经历的状态:可达,可复活,不可达



    2.1 可达

从根节点可以触及到这个对象,(什么是根节点:栈中引用的对象,方法区中静态成员或者常量引用的对象(全局对象),JNI方法栈中引用对象)



    2.2 可复活

一旦所有引用被释放,就是可复活状态,特殊状态:finalize()中可能复活该对象



    2.3 不可达

在finalize()后,可能会进入不可触及状态


不可触及的对象不可能复活

可以回收



   2.4 从finalize说起: 一个例子看清对象状态变迁

让我们来看个例子,体验对象从可达-》可复活-》可达-》不可达的过程

public class Student {
    public static Student obj;
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Student finalize called");
        obj=this;
    }
    @Override
    public String toString(){
        return "I am Student";
    }
    public static void main(String[] args) throws
            InterruptedException{
        obj=new Student();
        obj=null;   //可复活
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj is null");
        }else{
            System.out.println("obj is avilable");
        }
        obj=null;    //不可复活
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj is null");
        }else{
            System.out.println("obj is avilable");
        }
    }
}
public class Student {
    public static Student obj;
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Student finalize called");
        obj=this;
    }
    @Override
    public String toString(){
        return "I am Student";
    }
    public static void main(String[] args) throws
            InterruptedException{
        obj=new Student();
        obj=null;   //可复活
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj is null");
        }else{
            System.out.println("obj is avilable");
        }
        obj=null;    //不可复活
        System.gc();
        Thread.sleep(1000);
        if(obj==null){
            System.out.println("obj is null");
        }else{
            System.out.println("obj is avilable");
        }
    }
}

输出: 

Student finalize called

obj is avilable

obj is null

    当obj第一次引用设置成null 变成不可达,经过finalize()后变为可达对象,第二次设置成null由于finalize只执行一次,直接变为不可达对象。

  注:避免使用finalize(),操作不慎可能导致错误。可以使用try-catch-finally来替代它。GC的时间不确定,如果略掉

Thread.sleep(1000)上面的结果是不可预测的。





3 GC种类

  1. 串行收集器
  2. 并行收集器
  3. CMS收集器


 3.1Serial串行收集器

Java 产看gc命令_jvm_05

    最早且最稳定的收集器,但是由于采用串行的方式可能产生较长的停顿。

    新生代,老年带使用串行回收

    使用方式:-XX:+UseSerialGC,新生代、老年代使用串行回收

    使用的GC算法:新生代复制算法,老年代标记-压缩


[GC 0.844: [DefNew: 17472K->2176K(19648K), 0.0188339 secs] 17472K->2375K(63360K), 0.0189186 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

 [Full GC 8.259: [Tenured: 43711K->40302K(43712K), 0.2960477 secs] 63350K->40302K(63360K), [Perm : 17836K->17836K(32768K)], 0.2961554 secs] [Times: user=0.28 sys=0.02, real=0.30 secs]



3.2并行收集器

Java 产看gc命令_object_06



        3.2.1 ParNew并行收集器

        使用方式:-XX:+UseParNewGC,新生代并行(Serial新生带的并行版本),老年代串行

        使用GC算法:新生带使用的复制算法

        注:多线程需要多个核心支持,-XX:ParallelGCThreads 限制线程合理的线程数量,否则会产生效果

[ParNew: 8192K->1024K(9216K), 0.0612519 secs] 8192K->2007K(19456K), 0.0612942 secs] [Times: user=0.03 sys=0.00, real=0.06 secs] 

//todo 



       3.2.2 Parallel并行收集器

        serial串行收集器在,新老年代的并行化,新生代使用复制算法,老年代使用的标记压缩算法,但是默认情况老年代是串行,类似ParNew。

这是关注吞吐量的一个并行收集器。

        使用方式:-XX:+UseParallelGC  使用Parallel收集器+ 老年代串行

                           -XX:+UseParallelOldGC  使用Parallel收集器+ 并行老年代

PSYoungGen      total 9216K, used 8672K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)  eden space 8192K, 93% used [0x00000000ff600000,0x00000000ffd78ff0,0x00000000ffe00000)  from space 1024K, 99% used [0x00000000ffe00000,0x00000000ffeff1d8,0x00000000fff00000)  to   space 1024K, 0% used [0x00000000fff00000,0x00000000fff00000,0x0000000100000000) ParOldGen       total 72192K, used 67580K [0x00000000f3800000, 0x00000000f7e80000, 0x00000000ff600000)  object space 72192K, 93% used [0x00000000f3800000,0x00000000f79ff118,0x00000000f7e80000) Metaspace       used 4705K, capacity 5068K, committed 5248K, reserved 1056768K  class space    used 529K, capacity 564K, committed 640K, reserved 1048576K

        一些其他配置:

        -XX:MaxGCPauseMills

            最大停顿时间,单位毫秒,GC尽力保证回收时间不超过设定值

        -XX:GCTimeRatio

垃圾收集时间占总时间的比

 默认99,即最大允许1%时间做GC

 这两个参数是矛盾的。因为停顿时间和吞吐量不可能同时调优




3.3CMS(Concurrent Mark Sweep )收集器

      并发标记清除收集器是和用户线程一起执行,一定程度上降低了应用线程的等待时间,但是在并发阶段会降低吞吐量。

      使用方式:-XX:+UseConcMarkSweepGC,这是一个老年代的收集器

       CMS的执行过程如下图:

    

Java 产看gc命令_object_07

      CMS过程经历了四个过程,串行初始标记,并发标记,重新比较,并发清理四个阶段,为什么要四个阶段,

在经历初始比较后,由于在并发阶段可能导致对象的重新复活,所以需要使用重新标记。


[GC (Allocation Failure) [ParNew: 0K->0K(9216K), 0.0003332 secs][CMS: 193148K->80508K(194560K), 0.0065277 secs] 193148K->80508K(203776K), [Metaspace: 4697K->4697K(1056768K)], 0.0069152 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[GC (CMS Initial Mark) [1 CMS-initial-mark: 90748K(194560K)] 90748K(203776K), 0.0001216 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 0 K (9216 K)][Rescan (parallel) , 0.0001196 secs][weak refs processing, 0.0000058 secs][class unloading, 0.0009811 secs][scrub symbol table, 0.0003560 secs][scrub string table, 0.0002675 secs][1 CMS-remark: 121468K(194560K)] 121468K(203776K), 0.0019507 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]

 

par new generation   total 9216K, used 168K [0x00000000f3800000, 0x00000000f4200000, 0x00000000f4200000)  eden space 8192K,   2% used [0x00000000f3800000, 0x00000000f382a0a0, 0x00000000f4000000)  from space 1024K,   0% used [0x00000000f4000000, 0x00000000f4000000, 0x00000000f4100000)  to   space 1024K,   0% used [0x00000000f4100000, 0x00000000f4100000, 0x00000000f4200000) concurrent mark-sweep generation total 194560K, used 80508K [0x00000000f4200000, 0x0000000100000000, 0x0000000100000000) Metaspace       used 4704K, capacity 5068K, committed 5248K, reserved 1056768K  class space    used 529K, capacity 564K, committed 640K, reserved 1048576K

    好了经历上面的介绍,总结下CMS的特点:

    1 将标记时间进行了分阶段,尽可能的降低了停顿了,

    2 由于采用并发方式频繁的线程切换,会影响系统整体的吞吐量和性能,同时清理也是不彻底的操作,会产生比较大的内存碎片

    3 无法在空间快耗光时及时清理。虽然可以通过-XX:CMSInitiatingOccupancyFraction设置触发GC的阈值,但内存预留空间不够,就会引起concurrent mode failure。


[GC (CMS Initial Mark) [1 CMS-initial-mark: 147067K(194560K)] 147067K(203776K), 0.0001267 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.002/0.002 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (CMS Final Remark) [YG occupancy: 0 K (9216 K)][Rescan (parallel) , 0.0001196 secs][weak refs processing, 0.0000061 secs][class unloading, 0.0010007 secs][scrub symbol table, 0.0003919 secs][scrub string table, 0.0001430 secs][1 CMS-remark: 188027K(194560K)] 188027K(203776K), 0.0018660 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[CMS-concurrent-sweep-start]
[GC (Allocation Failure) [ParNew: 0K->0K(9216K), 0.0003316 secs][CMS[CMS-concurrent-sweep: 0.001/0.001 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
(concurrent mode failure): 188027K->126587K(194560K), 0.0088382 secs] 188027K->126587K(203776K), [Metaspace: 4699K->4699K(1056768K)], 0.0092257 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]