🍀 整體架構
🍀 強引用(默認支持模式)
當內存不足,JVM 開始垃圾回收,對於強引用的對象,就算是出現了 OOM 也不會對該對象進行回收,死都不收。
強引用是我們最常見的普通對象引用,只要還有強引用指向一個對象,就能表明對象還 “活着”,垃圾收集器不會碰到這種對象。在 Java 中最常見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即使該對象以後永遠都不會被用到 JVM 也不會回收。因此強引用是造成Java內存泄漏的主要原因之一。
對於一個普通的對象,如果沒有其他的引用關係,只要超過了引用的作用域或者顯式地將相應(強)引用賦值爲 null,一般認爲就是可以被垃圾收集的了(當然具體回收時機還是要看垃圾收集策略)。
📌 強引用 Case
package com.brian.interview.study.jvm.ref;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.ref
* Version: 1.0
* <p>
* Created by Brian on 2020/2/14 23:54
*/
public class StrongReferenceDemo {
public static void main(String[] args) {
Object obj1 = new Object(); // 這樣定義的默認就是強引用
Object obj2 = obj1; // obj2引用賦值
obj1 = null; // 置空
System.gc();
System.out.println(obj2);
}
}
🍀 軟引用
軟引用是一種相對強引用弱化了一些的引用,需要用 java.lang.ref.SoftReference 類來實現,可以讓對象豁免一些垃圾收集。
對於只有軟引用的對象來說,
軟引用通常用在對內存敏感的程序中,比如高速緩存就有用到軟引用,內存夠用的時候就保留,不夠用就回收!
📌 軟引用 Case
package com.brian.interview.study.jvm.ref;
import java.lang.ref.SoftReference;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.ref
* Version: 1.0
* <p>
* Created by Brian on 2020/2/15 10:35
*/
public class SoftReferenceDemo {
/**
* 內存夠用的時候就保留,不夠用就回收!
*/
public static void softRef_Memory_Enough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
/**
* JVM配置, 故意產生大對象並配置小的內存, 讓它內存不夠用了導致OOM, 看軟引用的回收情況
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRef_Memory_NotEnough() {
Object o1 = new Object();
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
try {
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Throwable e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
public static void main(String[] args) {
//softRef_Memory_Enough();
softRef_Memory_NotEnough();
}
}
🍀 弱引用
弱引用需要用 java.lang.ref.WeakReference 類來實現,它比軟引用的生存期更短,
對於只有弱引用的對象來說,只要垃圾回收機制一運行,不管 JVM 的內存空間是否足夠,都會回收該對象佔用的內存。
📌 弱引用 Case
package com.brian.interview.study.jvm.ref;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.util.HashMap;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.ref
* Version: 1.0
* <p>
* Created by Brian on 2020/2/15 10:56
*/
public class WeakReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
WeakReference<Object> weakReference = new WeakReference<>(o1);
System.out.println(o1);
System.out.println(weakReference.get());
o1 = null;
System.gc();
System.out.println("=========================");
System.out.println(o1);
System.out.println(weakReference.get());
}
}
📌 軟引用和弱引用的適用場景
假如有一個應用需要讀取大量的本地圖片:
- 如果每次讀取圖片都從硬盤讀取則會嚴重影響性能,
- 如果一次性全部加載到內存中有可能造成內存溢出。
此時使用軟引用可以解決這個問題。
設計思路:用一個 HashMap 來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在內存不足時,JVM 會自動回收這些緩存圖片對象所佔用的空間,從而有效地避免了 OOM 的問題。
HashMap<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
📌 談談 WeakHashMap
package com.brian.interview.study.jvm.ref;
import java.util.HashMap;
import java.util.WeakHashMap;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.ref
* Version: 1.0
* <p>
* Created by Brian on 2020/2/15 11:18
*/
public class WeakHashMapDemo {
public static void main(String[] args) {
myHashMap();
System.out.println("==============================");
myWeakHashMap();
}
private static void myWeakHashMap() {
WeakHashMap<Integer, String> map = new WeakHashMap<>();
Integer key = new Integer(2);
String value = "WeakHashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.out.println(map);
System.gc(); // 只要垃圾回收機制一運行,就會被回收
System.out.println(map + "\t" + map.size());
}
private static void myHashMap() {
HashMap<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.out.println(map);
System.gc();
System.out.println(map + "\t" + map.size());
}
}
///// 輸出結果
{1=HashMap}
{1=HashMap}
{1=HashMap} 1
==============================
{2=WeakHashMap}
{2=WeakHashMap}
{} 0
🍀 虛引用
虛引用需要 java.lang.ref.PhantomReference 類來實現
顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命週期。
如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它訪問對象,虛引用必須和引用隊列(ReferenceQueue)聯合使用。
虛引用的主要作用是跟蹤對象被垃圾回收的狀態。僅僅是提供了一種確保對象被 finalize 以後,做某些事情的機制。
PhantomReference 的 get 方法總是返回 null,因此無法訪問對應的引用對象。其意義在於說明一個對象已經進入 finalization 階段,可以被 gc 回收,用來實現比 finalization 機制更靈活的回收操作。
換句話說,設置虛引用關聯的唯一目的,就是在這個對象被收集器回收的時候收到一個系統通知或者後續添加進一步的處理。
Java 技術允許使用 finalize() 方法在垃圾收集器將對象從內存中清除出去之前做必要的清理工作。
📌 引用隊列
⏳ 引用隊列 Case
package com.brian.interview.study.jvm.ref;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.ref
* Version: 1.0
* <p>
* Created by Brian on 2020/2/15 12:07
*/
public class ReferenceQueueDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
System.out.println("========================");
o1 = null;
System.gc();
Thread.sleep(500);
System.out.println(o1);
System.out.println(weakReference.get());
System.out.println(referenceQueue.poll());
}
}
///// 輸出結果
java.lang.Object@330bedb4
java.lang.Object@330bedb4
null
========================
null
null
java.lang.ref.WeakReference@2503dbd3
📌 虛引用 Case
package com.brian.interview.study.jvm.ref;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
/**
* Copyright (c) 2020 ZJU All Rights Reserved
* <p>
* Project: JavaSomeDemo
* Package: com.brian.interview.study.jvm.ref
* Version: 1.0
* <p>
* Created by Brian on 2020/2/15 12:21
*/
/**
* java提供了4種引用類型, 在垃圾回收的時候, 都有自己各自的特點。
* ReferenceQueue 是用來配合引用工作的, 沒有 ReferenceQueue 一樣可以運行。
*
* 創建引用的時候可以指定關聯的隊列, 當 GC 釋放對象內存的時候, 會將引用加入到引用隊列,
* 如果程序發現某個虛引用已經被加入到引用隊列, 那麼就可以在所引用的對象的內存被回收之前採取必要的行動
* 這相當於是一種通知機制。
*
* 當關聯的引用隊列中有數據的時候, 意味着引用指向的堆內存中的對象被回收。通過這種方式, JVM 允許我們在對象被銷燬後,
* 做一些我們自己想做的事情。
*/
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(o1, referenceQueue);
System.out.println(o1);
System.out.println(phantomReference.get()); // PhantomReference 的 get 方法總是返回 null,因此無法訪問對應的引用對象
System.out.println(referenceQueue.poll());
System.out.println("====================================");
o1 = null;
System.gc();
Thread.sleep(500);
System.out.println(o1);
System.out.println(phantomReference.get());
System.out.println(referenceQueue.poll());
}
}
///// 輸出結果
java.lang.Object@330bedb4
null
null
====================================
null
null
java.lang.ref.PhantomReference@2503dbd3