1.背景
想要理解對象什么時候回收,就要理解到對象引用這個概念,於是有了下文
2.java中引用對象結構圖
3.引用詳解
3.1.什么是強引用
a.當內存不足,JVM開始垃圾回收,對於強引用的對象,就算是出現了00M也不會對該對象進行回收,死都不收。
b.強引用是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還“活着”,垃圾收集器不會碰這種對象。
在Java中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。
當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即使該對象以后永遠都不會被用到JVM也不會回收。
因此強引用是造成Java內存泄漏的主要原因之一
c.對於一個普通的對象,如果沒有其他的引用關系,只要超過了引用的作用域或者顯式地將相應(強)引用賦值為null,一般認為就是可以被垃圾收集的了〈當然具體回收時機還是要看垃圾收集策略)。
案例:

package com.wfd360.demo03GC.referDemo; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 12:12 * @description */ public class StrongRefer { /** * 強引用的理解 * * @param args */ public static void main(String[] args) { Object obj1 = new Object(); // 建立強引用 Object obj2 = obj1; // 觀察obj1 和 obj2 的各種內存地址 System.out.println("obj1=" + obj1); System.out.println("obj2=" + obj2); // obj1創建可以回收的條件 obj1 = null; // gc回收 System.gc(); // 觀察各對象情況 System.out.println("obj1=" + obj1); System.out.println("obj2=" + obj2); } }
從測試結果課程看出,obj1的實際對象別沒有回收;
3.2.什么是軟引用
a.軟引用是用來描述一些還有用但並非必需的對象,需要用java.lang.ref.SoftReference類來實現。
b.對於軟引用關聯着的對象,在系統將要發生內存溢出異常之前,將會把這些對象列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。在JDK1.2之后,提供了Soft Reference類來實現軟引用。
c.軟引用通常用在對內存敏感的程序中,比如高速緩存就有用到軟引用,內存夠用的時候就保留,不夠用就回收!
案例:

package com.wfd360.demo03GC.referDemo; import java.lang.ref.SoftReference; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 12:12 * @description */ public class SoftRefer { /** * 軟引用的理解 * 通過設置jvm參數,在不同的條件下觀察 * * @param -Xms5m -Xmx5m -XX:+PrintGCDetails * @param args */ public static void main(String[] args) { // 測試內存充足(不回收軟引用) //testSoftReferNOGc(); // 測試內存不充足(回收軟引用) testSoftReferGc(); } /** * 模擬內存充足的情況 */ public static void testSoftReferNOGc() { Object obj1 = new Object(); // 建立軟引用 SoftReference softRefer = new SoftReference<>(obj1); // 觀察內存地址 System.out.println("obj1=" + obj1); System.out.println("softRefer=" + softRefer.get()); // obj1創建可以回收的條件 obj1 = null; // gc回收 System.gc(); // 再次觀察內存地址 System.out.println("obj1=" + obj1); System.out.println("softRefer=" + softRefer.get()); } /** * 模擬內存不足 * 1.設置較小的堆內存 * 2.創建大對象 * 3.jvm參 * -Xms5m -Xmx5m -XX:+PrintGCDetails */ public static void testSoftReferGc() { Object obj1 = new Object(); // 建立軟引用 SoftReference softRefer = new SoftReference<>(obj1); // 觀察內存地址 System.out.println("obj1=" + obj1); System.out.println("softRefer=" + softRefer.get()); // obj1創建可以回收的條件 obj1 = null; try { byte[] bytes = new byte[6 * 1024 * 1024]; } catch (Throwable e) { System.out.println("===============>error:" + e.getMessage()); } finally { // 再次觀察內存地址 System.out.println("obj1=" + obj1); System.out.println("softRefer=" + softRefer.get()); } } }
內存充足測試結果:
內存不充足測試結果:
實際案例
假如有一個應用需要讀取大量的本地數據(圖片、通訊率、臨時文件等):
如果每次讀取數據都從硬盤讀取則會嚴重影響性能,
如果一次性全部加載到內存中又可能造成內存溢出。
此時使用軟引用可以解決這個問題。
設計思路是:用一個HashMap來保存數據的路徑和相應數據對象關聯的軟引用之間的映射關系,在內存不足時,
JVM會自動回收這些緩存數據對象所占用的空間,從而有效地避免了00M的問題。
Map<String,SoftReference>imageCache=new HashMap<String,SoftReference>();
3.3.什么是弱引用
a.弱引用也是用來描述非必需對象的,但是它的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次垃圾收集發生之前。
b..當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。在JDK1.2之后,提供廣Weak Reference類來實現弱引用。
c.弱引用需要用Java.lang.ref.WeakReference類來實現,它比軟引用的生存期更短.
案例:

package com.wfd360.demo03GC.referDemo; import java.lang.ref.WeakReference; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 12:12 * @description */ public class WeakRefer { /** * 弱引用的理解 * * @param args */ public static void main(String[] args) { Object obj1 = new Object(); // 建立弱引用 WeakReference softRefer = new WeakReference<>(obj1); // 觀察內存地址 System.out.println("obj1=" + obj1); System.out.println("softRefer=" + softRefer.get()); // obj1創建可以回收的條件 obj1 = null; // gc回收 System.gc(); // 再次觀察內存地址 System.out.println("obj1=" + obj1); System.out.println("softRefer=" + softRefer.get()); } }
擴展知識-WeakHashMap
查看API介紹:
測試代碼:

package com.wfd360.demo03GC.referDemo; import java.util.HashMap; import java.util.WeakHashMap; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 5:10 * @description <p> * 弱引用引用之:WeakHashMap * 以弱鍵 實現的基於哈希表的 Map。在 WeakHashMap 中,當某個鍵不再正常使用時,將自動移除其條目。 * 更精確地說,對於一個給定的鍵,其映射的存在並不阻止垃圾回收器對該鍵的丟棄,這就使該鍵成為可終止的,被終止, * 然后被回收。丟棄某個鍵時,其條目從映射中有效地移除,因此,該類的行為與其他的 Map 實現有所不同。 * </p> */ public class WeakReferMap { /** * 測試 HashMap 與 WeakHashMap 區別 * 測試邏輯: * 1.創建不同的map * 2.創建key value值 * 3.放入各自的map,並打印結果 * 4.將key設置為null,並打印結果 * 5.手動GC,並打印結果 * * @param args */ public static void main(String[] args) { hashMapMethod(); System.out.println("--------華麗的分割線--------"); weakHashMapMethod(); } /** * HashMap測試(強引用) */ private static void hashMapMethod() { HashMap<String, String> map = new HashMap<>(); String key = "key1"; String value = "HashMap-value"; map.put(key, value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); } /** * 若引用(WeakHashMap測試) */ private static void weakHashMapMethod() { WeakHashMap<String, String> map = new WeakHashMap<>(); // 注意這里的new一個字符串與直接寫key="key2"對測試結果是有區別的,詳細原因可以看之前講的內存分配 String key = new String("key2"); String value = "WeakHashMap-value"; map.put(key, value); System.out.println(map); key = null; System.out.println(map); System.gc(); System.out.println(map); } }
測試結果:
從測試結果可以看出:弱引用的map數據已經被回收。
擴展知識-ReferenceQueue引用隊列
代碼:

package com.wfd360.demo03GC.referDemo; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 7:23 * @description */ public class QueueRefer { /** * 測試弱引用回收前,把數據放入隊列中 * * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue(); // 當GC釋放對象內存的時候,會將引用加入到引用隊列 WeakReference<Object> weakReference = new WeakReference<>(obj1, referenceQueue); System.out.println(obj1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); System.out.println("--------華麗的分割線--------"); obj1 = null; System.gc(); Thread.sleep(500); System.out.println(obj1); System.out.println(weakReference.get()); System.out.println(referenceQueue.poll()); } }
采用弱引用的方式測試結果:
從測試結果可以看出,需要回收的對象已經進入隊列。
采用軟引用的方式測試結果:
從測試結果可以看出,軟引用,沒有到達回收的條件,並沒有進行回收,也不會進入隊列;
3.4.什么是虛引用
1.虛引用需要java.lang.ref.PhantomReference類來實現。
2.與其他幾種引用都不同,虛引用並不會決定對象的生命周期。如果一個對象僅持有
虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它訪
問對象,虛引用必須和引用隊列(ReferenceQueue)聯合使用。
3.虛引用的主要作用是跟蹤對象被垃圾回收的狀態。僅僅是提供了一種確保對象被finalize以后,做某些事情的
機制。PhantomReference的get方法總是返回null,因此無法訪問對應的引用對象。其意義在於說明一個對象己
經進入俑finalization階段,可以被gc回收,用來實現比finalization機制更靈活的回收操作。
4.設置虛引用關聯的唯一目的,就是在這個對象被收集器回收的時候收到一個系統通知或者后續添加
進一步的處理。Java技術允許使用finalize()方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。
代碼:

package com.wfd360.demo03GC.referDemo; import java.lang.ref.PhantomReference; import java.lang.ref.ReferenceQueue; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 7:44 * @description */ public class PhantomRefer { /** * 虛引用測試 * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { Object obj1 = new Object(); ReferenceQueue<Object> referenceQueue = new ReferenceQueue(); PhantomReference<Object> phantomReference = new PhantomReference<>(obj1,referenceQueue); System.out.println(obj1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); System.out.println("--------華麗的分割線--------"); obj1 = null; System.gc(); Thread.sleep(500); System.out.println(obj1); System.out.println(phantomReference.get()); System.out.println(referenceQueue.poll()); } }
測試結果:
4.重要總結
對象是否存活判斷流程:
1.可達性分析,看是否有GC Roots的引用鏈,如果沒有將做第一次標記;
2.檢查是否需要執行finalize()方法,
如果沒必要(之前執行過了),直接回收內存;
如果要執行finalize()方法,這個時候對象如果再次建立引用鏈(唯一自救機會),對象不會被回收,否則直接回收;
總結:
1.對象回收滿足兩個條件:
a.沒有引用鏈。
b.回收前會執行finalize()方法,如果執行finalize(),沒有再次建立連接(如果重新與引用鏈上的任意對象建立連接,例如給對象賦值,該對象都不會被回收)
2.在gc回收前會執行finalize()方法,只執行一次,並且是異步執行不保證執行成功,線程優先級低
代碼演示:

package com.wfd360.demo03GC.referDemo; /** * @author 姿勢帝-博客園 * @address https://www.cnblogs.com/newAndHui/ * @WeChat 851298348 * @create 06/20 8:34 * @description */ public class FinalizeGC { public static FinalizeGC obj1 = null; /** * 重寫finalize方法 * @throws Throwable */ @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("執行finalize方法"); // 自救,在回收時建立引用鏈 FinalizeGC.obj1 = this; } public static void main(String[] args) throws InterruptedException { obj1 = new FinalizeGC(); obj1 = null; System.gc(); Thread.sleep(600); System.out.println("第一次自救成功:"+obj1); obj1 = null; System.gc(); Thread.sleep(600); System.out.println("第二次自救失敗,不會再次執行finalize方法:"+obj1); } }
測試結果:
完美!