GC

查看GC日志时需要用到的虚拟机参数:

-XX:+PrintGC 输出GC日志
-XX:+PrintGCDetails 输出GC的详细日志
-XX:+PrintGCTimeStamps 输出GC的时间戳(以基准时间的形式)
-XX:+PrintGCDateStamps 输出GC的时间戳(以日期的形式,如2017-12-13T12:07:59.234+0800)
-XX:+PrintHeapAtGC 在进行GC的前后打印出堆的信息
-Xloggc:../logs/gc.log 日志文件的输出路径

1.引用计数算法

给对象中添加一个医用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。
测试:

/**
 * VM Args:-XX:+PrintGCDetails
 */
public class ReferenceCountingGC {

    public Object instance = null;

    private static final int _1MB = 1024*1024;

    /**
     * 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否被回收过
     */
    private byte[] bigSize = new byte[2*_1MB];

    public static void testGc(){
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

        System.gc();
    }

    public static void main(String[] args) {
        ReferenceCountingGC.testGc();
    }
}

运行结果:

[GC [PSYoungGen: 6759K->712K(38400K)] 6759K->712K(124928K), 0.0011986 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC [PSYoungGen: 712K->0K(38400K)] [ParOldGen: 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen: 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 38400K, used 2330K [0x00000007d5c00000, 0x00000007d8680000, 0x0000000800000000)
  eden space 33280K, 7% used [0x00000007d5c00000,0x00000007d5e46810,0x00000007d7c80000)
  from space 5120K, 0% used [0x00000007d7c80000,0x00000007d7c80000,0x00000007d8180000)
  to   space 5120K, 0% used [0x00000007d8180000,0x00000007d8180000,0x00000007d8680000)
 ParOldGen       total 86528K, used 610K [0x0000000781400000, 0x0000000786880000, 0x00000007d5c00000)
  object space 86528K, 0% used [0x0000000781400000,0x0000000781498b00,0x0000000786880000)
 PSPermGen       total 21504K, used 2927K [0x000000077c200000, 0x000000077d700000, 0x0000000781400000)
  object space 21504K, 13% used [0x000000077c200000,0x000000077c4dbf70,0x000000077d700000)

GC日志分析:

[GC [PSYoungGen(使用PSYoungGen作为年轻代的垃圾回收器): 6759K(年轻代垃圾回收前的大小)->712K(年轻代垃圾回收以后的大小)(38400K)(年轻带的总大小)] 6759K(堆区垃圾回收前的大小)->712K(堆取垃圾回收后的大小)(124928K)(堆区总大小), 0.0011986 secs(回收时间)] [Times: user=0.00(Young GC用户耗时) sys=0.00(Young GC系统耗时), real=0.00 secs(Young GC实际耗时)] 
[Full GC [PSYoungGen(年轻代): 712K->0K(38400K)] [ParOldGen(年老代): 0K->610K(86528K)] 712K->610K(124928K) [PSPermGen(持久代): 2910K->2909K(21504K)], 0.0099723 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
下面则是堆的具体信息,暂时笔者还未能解读

从上述的运行结果可以看到,进行GC后,年轻代的空间被GC进行了极大的清理,则说明objA和objB之间的循环引用并没有影响到GC回收,说明虚拟机采用的并不是引用计数算法。

2、可达性分析算法

在可达性分析算法中,一个很重要的概念就是“GC Roots”,"GC Root"顾名思义即是GC进行的根,GC会判断堆中的对象是否与“GC Roots”能够通过某条线路连接到。如下图所示:


GC_第1张图片
gc-root.png

图中object1~object4都与GC Roots能够连接到,因此不能回收,可以存活,而object5~object7无法连接,因此是可以回收的。
可以作为GC Roots的对象:
1、栈中引用的对象。根据内存模型知道,栈中是正在运行的线程存放的数据,因此当正在运行的程序在引用的对象不能回收,不然会造成问题。
2、方法区中静态属性引用的对象。静态属性会在堆中存一份对象数据,等待调用,这个也是不允许回收的。
3、方法区中常量引用的对象。方法区中有运行时常量池,在常量池中引用的对象也是不允许回收的。
4、本地方法栈中引用的对象。

3、强、软、弱、虚引用

无论用引用计数算法判断对象的引用数量,还是可达性分析算法判断引用链是否可达,都需要判断对象的引用。JDK1.2后,将引用分为四类:强引用(Strong Reference),软引用(SoftReference),弱引用(WeakReference),虚引用(PhantomReference)。这四种引用强度依次减弱。
强引用:普通的引用方法。例如new一个对象,Object obj = new Object();只有在这种引用关系失效的时候,GC才会考虑回收这个对象。
测试代码:

/**
 * VM args:-XX:+PrintGCDetails -XX:+PrintAssembly
 */
public class StrongReferenceTest {

    public static void main(String[] args) {
        Object referent = new Object();

        /**
         * 通过赋值创建strongReference,此时new Object对象同事被两个变量引用到
         */
        Object strongReference = referent;

        System.out.println(referent);
        System.out.println(referent.equals(strongReference));
        referent = null;

        System.gc();

        System.out.println(strongReference);
    }
}

测试结果:

java.lang.Object@4b1210ee
true
[Full GC (System.gc()) [Tenured: 0K->712K(87424K), 0.0029994 secs] 2795K->712K(126720K), [Metaspace: 3210K->3210K(1056768K)], 0.0030517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
java.lang.Object@4b1210ee
Heap
 def new generation   total 39424K, used 1754K [0x0000000081400000, 0x0000000083ec0000, 0x00000000ab800000)
  eden space 35072K,   5% used [0x0000000081400000, 0x00000000815b6850, 0x0000000083640000)
  from space 4352K,   0% used [0x0000000083640000, 0x0000000083640000, 0x0000000083a80000)
  to   space 4352K,   0% used [0x0000000083a80000, 0x0000000083a80000, 0x0000000083ec0000)
 tenured generation   total 87424K, used 712K [0x00000000ab800000, 0x00000000b0d60000, 0x0000000100000000)
   the space 87424K,   0% used [0x00000000ab800000, 0x00000000ab8b2078, 0x00000000ab8b2200, 0x00000000b0d60000)
 Metaspace       used 3232K, capacity 4494K, committed 4864K, reserved 1056768K
  class space    used 354K, capacity 386K, committed 512K, reserved 1048576K

软引用:比强引用稍弱的引用方法。当某个对象只有这一个软引用存在,且内存不足的时候,GC会考虑回收这部分资源。
如:

Object obj = new Object();
SoftReference softReference = new SoftReference<>(obj);
 
 

当使obj = null时,就只有softReference引用了Object对象,如果内存不足,则GC就会释放这部分资源,当内存充足时,这部分资源会依然存在。
测试代码:

/**
 * VM args:-XX:+PrintGCDetails
 */
public class SoftReferenceTest {

    public static void main(String[] args) {

        Object referent = new Object();

        /**
         * 通过赋值创建softReference
         */
        SoftReference softReference = new SoftReference(referent);

        System.out.println(referent.equals(softReference.get()));
        referent = null;

        System.gc();
        System.out.println(softReference.get());
    }
}
 
 

测试结果:

true
java.lang.Object@4b1210ee

弱引用:比软引用强度更弱。不管内存够不够用,在下一次GC时,发现某对象只有弱引用时,GC会清理释放这部分资源。
测试代码:

/**
 *
 */
public class WeakReferenceTest {

    public static void main(String[] args) {
        Object referent = new Object();

        /**
         * 通过赋值创建weakReference
         */
        WeakReference weakReference = new WeakReference(referent);

        System.out.println(referent.equals(weakReference.get()));
        referent = null;

        System.gc();

        System.out.println(weakReference.get());
    }
}
 
 

测试结果:

true
null

虚引用:是最弱的一种引用类型。GC回收时对于虚引用会当做没有引用存在一样。
测试代码:

/**
 * phantom reference 的 get 方法永远返回 null
 * PhantomReference 唯一的用处就是跟踪 referent何时被 enqueue 到 ReferenceQueue 中.
 * 当一个 WeakReference 开始返回 null 时, 它所指向的对象已经准备被回收,
 * 这时可以做一些合适的清理工作. 将一个 ReferenceQueue 传给一个 Reference 的构造函数,
 * 当对象被回收时, 虚拟机会自动将这个对象插入到 ReferenceQueue 中,
 * WeakHashMap 就是利用 ReferenceQueue 来清除 key 已经没有强引用的 entries.
 */
public class PhantomReferenceTest {
    public static void main(String[] args) {

        Object referent = new Object();

        /**
         * 通过赋值创建phantomReference
         */
        PhantomReference phantomReference = new PhantomReference<>(referent,new ReferenceQueue());

        /**
         * phantom reference 的 get 方法永远返回 null
         */
        System.out.println(referent.equals(phantomReference.get()));
        referent = null;

        System.gc();
        System.out.println(phantomReference.get());
    }
}
 
 

测试结果:

false
null

引用的用途

WeakReference:当对对象结构和拓扑不是很清晰的时候,可以通过弱引用,可以合理的释放对象,而不会造成不必要的内存泄漏。
如:

A a = new A();
WeakReference wr = new WeakReference(a);
//B b = new B(a);

引用关系如下图:


GC_第2张图片
image.png

当使a=null时,A就只有弱引用依赖,因此GC会立刻回收A这个对象。
SoftReference:软引用有个很重要的特性就是在内存充足时会保留资源,当内存不足时会释放资源,因此很适合用来做缓存处理。
摘取网络上的一个应用softReference的代码:

public class ImageLoader {  
      
    private Map> cacheImage = new HashMap>();  
      
    public void loadImage(final String path,final Callback callback){  
        SoftReference softReference = cacheImage.get(path);  
        if(softReference!=null){  
            Bitmap bm = softReference.get();  
            if(bm!=null){  
                callback.execute(bm);  
                return;  
            }  
        }  
        new Thread(new Runnable() {  
           public void run() {  
            HttpClient client = new DefaultHttpClient();  
            try {  
                HttpResponse response = client.execute(new HttpGet(path));  
                HttpEntity entity = response.getEntity();  
                byte []bs= EntityUtils.toByteArray(entity);  
                final Bitmap bm = BitmapFactory.decodeByteArray(bs, 0,bs.length);  
                SoftReference reference = new SoftReference(bm);   
                cacheImage.put(path,reference);  
                callback.execute(bm);  
                entity.consumeContent();  
            } catch (Exception e) {  
                e.printStackTrace();  
            }  
            }  
        }).start();  
    }  
      
    public static abstract class Callback{  
        abstract void execute(Bitmap bm);  
    }  
}

这个就是用软引用来达到缓存处理的方法,这个在andriod中应用得较多,然而有人发现这个方法处理缓存并不是很理想,因此慢慢也在被人抛弃。因为当有10个对象只有软引用时,GC不知道到底clear哪几个或者keep哪几个,甚至,gc会不知道到底是clear资源还是扩展heap。
PhantomReference:虚引用要和ReferenceQueue搭配使用。
在构造Reference对象时,有两种构造函数可供选择:

/* -- Constructors -- */

    Reference(T referent) {
        this(referent, null);
    }

    Reference(T referent, ReferenceQueue queue) {
        this.referent = referent;
        this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
    }

一种是带ReferenceQueue的,另一种不带此参数。
根据http://blog.csdn.net/u012332679/article/details/57489179所说:

ReferenceQueue这个类有什么用呢,它跟Reference有什么关系呢,关系主要体现在这几个方面,首先Reference这个类里面在构造函数的时候有两种选择,一种是给它传入一个ReferenceQueue,一种是不传,如果不传的话,等这个对象的内存被回收了,直接从Active变为Inactive状态,如果我们传入了ReferenceQueue,那么当对象的内存回收的时候会经历一个过程,从Active->Pending->Enqueued->Inactive。pending状态就是等待着进入ReferenceQueue队列的这样一个状态,说白了它目前还没被回收,只是对象的引用(用户代码中的引用)被移除了,pending保存了这个引用,回收的过程中,ReferenceHandler这个线程会把该对象的引用(pending)放入到我们在构造函数时传入的那个队列里面

ReferenceQueue就是当一个对象gc调后,对象的信息会再保存一段时间,以便我们能够进行额外的操作。
有一个问题:当在ReferenceQueue中发现SoftReference或WeakReference对象时,并不能确定该对象引用的对象已被销毁,此时的对象只是进入了Finalizable状态,然而如果使用的是PhantomReference,在ReferenceQueue中发现了PhantomReference对象,此时referent则已经销毁了。

你可能感兴趣的:(GC)