一、概述

前面我们已经知道,栈的内存是固定的,栈帧多大都是已知。而堆就不一样了。

二、判断对象存活状态

垃圾回收的第一件事就是判断对象是否还活着,是否可以回收。

1.引用计数法

  1. 给对象添加一个引用计数器,被引用时加一,失效时减一,计数器为0就不能被使用了。这种方法无法解决相互引用问题。

public class ReferenceCountingGC {
    public Object instance = null;
    private static final int _1M = 1024 * 1024;
    //很大的内存
    private byte[] bigSize = new byte[2 * _1M];

    public static void main(String[] args) {
        testGC();
    }

    public static void testGC() {

        ReferenceCountingGC obj1 = new ReferenceCountingGC();
        ReferenceCountingGC obj2 = new ReferenceCountingGC();
        obj1.instance = obj2;
        obj2.instance = obj1;

        obj1 = null;
        obj2 = null;

        System.gc();
    }
}

这里的日志信息: TODO 日志分析,gc文件查看


4603k -> 210k

意味着并没有因为相互引用就不回收,说明虚拟机并不是通过引用计数实现的。

2. 可达性分析算法

  1. 主流语言都是通过可达性分析实现垃圾回收。就是通过一系列GCRoots作为七点,判断有没有被引用。如果一个对象到GCRoots没有引用链,用图论的语言就是不可达,那么可以回收。
  2. java的GCRoots包括 虚拟机栈(栈帧的本地变量表)引用的对象。、方法区中类静态属性引用的对象、方法区中常量引用的对象,本地方法(native方法)引用的对象。

3.理解引用

  1. 引用实际上很重要,上面的两张方法都是通过引用来判断。如果reference类型的数据中存储的数值代表另一个内存的起始地址,就称这块内存代表一个引用。
  2. 一个对象在这种定义下只有引用和被引用两种关系。所以这个定义太过狭隘。
  3. jdk1.2后java对引用的概念进行了扩充。分为强引用、软引用、弱引用、虚引用。
  4. 四种引用相关的只是可以查询资料。

4.最后的判断

  1. 即使判断为不可达对象,也只是处于“缓刑”阶段。要彻底宣告死亡还得经过两次判断。第一次标记后进行筛选,筛选条件是该对象是否有必要执行finalize方法,当对象没有覆盖finalize方法或者已经被调用过,将会被标记为没必要执行。
  2. 如果被标记为有必要执行,会被放在一个F-Queue中,稍后放在一个虚拟机级别的线程中执行这个finalize,但是不会等待它执行。
  3. finalize是对象拯救自己最后的机会,只要把自己引用到某个对象即可。但是每个对象的finalize只会调用一次,所以下面的代码拯救自己一次。第二次失败了。

public class FinalizeEscapeGC {

    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive(){
        System.out.println("yes , i am alive");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Exception {
        SAVE_HOOK = new FinalizeEscapeGC();

        // 对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();

        //finalize 优先级低,等待执行
        Thread.sleep(1000);

        if (null != SAVE_HOOK) {
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no, i am dead");
        }

        //对象第二次拯救自己
        SAVE_HOOK = null;
        System.gc();
        //finalize 优先级低,等待执行
        Thread.sleep(1000);

        if (null != SAVE_HOOK) {
            SAVE_HOOK.isAlive();
        }else {
            System.out.println("no, i am dead");
        }
    }
}

这种方式太不推荐了。

5.回收方法区

  1. 很多人认为方法去是没有垃圾回收的,其实只是方法区垃圾回收效率低。
  2. 永久代的垃圾回收主要是两部分,一部分是常量,另一部分是无用的类。废弃常量和堆的回收很类似,但是判断一个类是否是无用的类,就比较麻烦。
  3. 无用的类判断条件是:所有实例都被回收了,ClassLoader被回收了,无法在任何地方通过反射得到该类的方法。
  4. 在大量使用反射、动态代理、CGlib等技术的地方,频繁自定义classLoader的地方都要虚拟机具备类卸载的功能,保证永久代不溢出。

三、垃圾回收算法

1.标记-清楚

对需要回收的对象标记,然后回收。标记和清除效率都不高,而且容易产生碎片。

2.复制算法

将空间分为相同的两块,每次回收后移动到另一边。存活率较高时效率低,另外浪费一半空间

3.标记-整理

标记然后移动。

4.分代回收

对不同对象用不同方法。

四、算法实现

TODO

五、常见垃圾收集器

1.TODO

2.阅读GC日志

gc 参数:

JVM的GC日志的主要参数包括如下几个:

-XX:+PrintGC 输出GC日志

-XX:+PrintGCDetails 输出GC的详细日志

-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)

-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如 2013-05-04T21:53:59.234+0800)

-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息

-Xloggc:../logs/gc.log 日志文件的输出路径

日志:

0.756: [Full GC (System) 0.756: [CMS: 0K->1696K(204800K), 0.0347096 secs] 11488K->1696K(252608K), [CMS Perm : 10328K->10320K(131072K)], 0.0347949 secs] [Times: user=0.06 sys=0.00, real=0.05 secs]  
5.617: [GC 5.617: [ParNew: 43296K->7006K(47808K), 0.0136826 secs] 44992K->8702K(252608K), 0.0137904 secs] [Times: user=0.03 sys=0.00, real=0.02 secs]  

解释如下:

5.617(时间戳): [GC(Young GC) 5.617(时间戳): [ParNew(使用ParNew作为年轻代的垃圾回收器): 43296K(年轻代垃圾回收前的大小)->7006K(年轻代垃圾回收以后的大小)(47808K)(年轻代的总大小), 0.0136826 secs(回收时间)] 44992K(堆区垃圾回收前的大小)->8702K(堆区垃圾回收后的大小)(252608K)(堆区总大小), 0.0137904 secs(回收时间)] [Times: user=0.03(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.02 secs(Young GC实际耗时)]  

第一个是时间戳,然后是GC或者FullGC代表垃圾回收类型。然后中括号括起来的是年轻代垃圾回收,第一个是垃圾回收器,例如:DefNew,PSYoungGen等,然后是年轻代的容量变化和总用量。中括号外的是堆的总容量。 后面三个是时间,分别是user,sys,real。分别是用户态CPU时间、内核态CPU时间、墙钟时间,墙钟时间包含各种非运算的等待耗时,例如IO阻塞,CPU时间则不包含这些世界,当计算机是多核这些世界会累加,所以看到real或者sys超过real也正常。

3.垃圾收集器参数