Java 四大引用詳解

一、 GC回收日誌打印基本設置

啓動設置:

-verbose:gc             //開啓gc日誌    
-XX:+PrintGCDetails     //打印gc詳情
-XX:+PrintGCDateStamps  //打印gc時間戳
-XX:+PrintHeapAtGC      //在進行GC的前後打印出堆的信息
-Xloggc:gc.log          //日誌輸出名稱
-Xms3M                  //初始內存
-Xmx4M                  //最大可用內存

日誌各個字段含義簡述:

2019-03-28  日期

10:02:40.628+0800: 執行回收時間 

0.080: 啓動JVM到該次垃圾回收時間

GC/Full GC :垃圾回收類型

Allocation Failure : 垃圾回收的原因,此時爲Eden區空間不足

ParNew/CMS/Metaspace : 垃圾回收區域 新生代/老年代/元空間

[PSYoungGen: 510K->504K(1024K)]  清理前佔用 -> 清理後佔用(新生代內存總大小)

PS代表新生代的是使用的是Parallel Scavenge收集器垃圾收集器

[ParOldGen]  Par代表老年代使用的是Parallel old垃圾回收器

0.0006474 secs 表示回收的時間

510K->536K(3584K)  整個堆內存清理前佔用 ——> 清理後佔用(對內存總大小)

[Metaspace: 3304K->3304K(1056768K)] 這部分表示的是方法區的內存大小變化

[Times: user=0.00 sys=0.00, real=0.00 secs]

user 代表的是用戶狀態耗時0.00,

sys 系統狀態耗時0.00,

real CPU實際的執行時間爲0.00

Ergonomics:HotSpot自動選擇和調優引發的FullGC

二、瞭解其概念及其區別

我們在實際開發中,往往會執行一些方法加載一些參數值到內存中,後期隨着業務的發展、方法的執行等等大量的數據存在內存中,結果導致內存佔用過多而產生的溢出異常,因此我們期望自己的放到內存中的對象也具有生命週期(如:內存不足時,jvm會自動回收掉某些對象從而避免OOM的錯誤)這個時候就需要用到軟引用和弱引用

從JDK 1.2之後,Java 對引用的概念進行了擴充,將引用分爲了:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4 種,這 4 種引用的強度依次減弱。

強引用:

Java中默認聲明的就是強引用,如:Object object=new Object();只要強引用還存在,JVM必定不會回收掉被引用的對象,即使在內存不足的情況下,JVM寧願拋出OOM異常,也不會回收這種對象;
如下代碼:

public class StronglyReference {
    public static void main(String[] args) {
        Object object=new Object();
        System.gc();
        System.out.println("GC回收後:"+object);
        try {
            byte[] buff2 = new byte[1024 * 1024 * 4];
        } catch (OutOfMemoryError e) {
            //爲了程序繼續執行下去,捕獲異常,如果不捕獲會拋出異常且回收
            System.out.println("創建對象發生OOM異常,哥,你寧願發生異常也不回收掉哎,何必呢!");
        }
        //將引用設置爲null,jvm在適當的時候會回收該對象
        object=null;
        System.out.println("設置爲空後:"+object);
    }
}

結果產出:

GC回收後:java.lang.Object@12a3a380
創建對象發生OOM異常,哥,你寧願發生異常也不回收掉哎,何必呢!
設置爲空後:null

經過上面的demo實例,然後分析GC日誌,我們得出下面的問題和答案;
問:那麼在使用強引用的時候,JVM是麼時候會觸發回收?
答:

  1. 線程結束時回收;
  2. 當 softReference 設置爲空的時候,會被回收;

軟引用(SoftReference):

官方解釋:軟引用是用來描述一些非必需但仍有用的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的內存,纔會拋出內存溢出異常。在 JDK1.2 之後,用java.lang.ref.SoftReference類來表示軟引用。
查看一下事例:

public class SoftReferences {
    public static void main(String[] args) {
        List<SoftReference> list=new ArrayList<SoftReference>();
        for (int i=0;i<10;i++){
            byte[] softBuff = new byte[1024 * 1024 * 1];
            SoftReference sr = new SoftReference(softBuff);
            list.add(sr);
        }
        for (SoftReference sr:list) {
            System.out.println(sr.get());
        }
    }
}

返回結果:

null
null
null
null
null
null
null
null
null
[B@12a3a380

在GC日誌中我們可以看到一個關鍵字出現:Ergonomics(解釋上面有說明)
由此我們可以得出一個結論與解釋一致
(還有一種情況,當軟引用設置爲空時也會被回收)
事例代碼:

public static void main(String[] args) {
    byte[] softBuff1 = new byte[1024 * 1024 * 2];
    SoftReference sr1 = new SoftReference(softBuff1);
    sr1=null;
    System.out.println(sr1.get());
}

result:

Exception in thread "main" java.lang.NullPointerException

由此可見,設置爲空時,對象也被回收了;
弱引用(WeakReference):
這個其實挺簡單的:只要JVM進行垃圾回收,無論內存是否充足,都會回收弱引用關聯的對象,在 java 中,用 java.lang.ref.WeakReference類來表示
事例:

public class WeakReferences {
    public static void main(String[] args) {
        try {
            WeakReference w1=new WeakReference(new Object());
            System.out.println(w1.get());
            System.gc();
            System.out.println(w1.get());
        } catch (NullPointerException e) {
            System.out.println("W1 會拋出空指針異常,表示被回收");
        }
    }
}

result:

java.lang.Object@12a3a380
null

虛引用(PhantomReference):

官方解釋:如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,它隨時可能會被回收,在 JDK1.2 之後,用PhantomReference 類來表示;我們看下源碼
源碼:

public class PhantomReference<T> extends Reference<T> {
    
    public T get() {
        return null;
    }
    
    public PhantomReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

通過源碼我們可以清晰的瞭解到,它只有一個構造函數和一個 get()方法,而且 get()方法是固定的返回的null,這樣玩們就知道了,我們永遠無法單獨的通過虛引用來獲取對象,虛引用也必須要和ReferenceQueue引用隊列一起使用。這是是它和軟引用、弱引用最直觀的局別;

引用隊列(ReferenceQueue)

官方解釋:引用隊列可以與軟引用、弱引用以及虛引用一起配合使用,當垃圾回收器準備回收一個對象時,如果發現它還有引用,那麼就會在回收對象之前,把這個引用加入到與之關聯的引用隊列中去。程序可以通過判斷引用隊列中是否已經加入了引用,來判斷被引用的對象是否將要被垃圾回收,這樣就可以在對象被回收之前採取一些必要的措施。

與軟引用、弱引用不同,虛引用必須和引用隊列一起使用(上面有提到過)。

public class PhantomReferences {
    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue queue=new ReferenceQueue();
        Object phantomObject=new Object();
        PhantomReference phantomReference=new PhantomReference (phantomObject,queue);
        System.out.println(phantomReference.get());
        phantomObject=null;
        System.gc();
        System.out.println(phantomReference.get());
        Thread.sleep(200);
        System.out.println(queue.poll());
    }
}

result:

null
null
java.lang.ref.PhantomReference@12a3a380

問:爲什麼會有對象輸出呢?
答:我們已經創建引用隊列,並且在引用隊列裏面創建了對象phantomObject,當GC回收的時候發現虛引用,會將這個對象丟到隊列裏面,而現在插入隊列的時候發現已經有了這個對象,會失敗,然後會回收掉這個對象並且返回,所以會有返回對象

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章