Java中的引用
Java中的引用
前言
在原來的時候,我們談到一個類的實例化
Person p = new Person()
在等號的左邊,就是一個對象的引用,存儲在棧中
而等號右邊,就是實例化的對象,存儲在堆中
其實這樣的一個引用關係,就被稱為強引用
整體架構
強引用
當記憶體不足的時候,JVM開始垃圾回收,對於強引用的對象,就算是出現了OOM也不會對該對象進行回收,打死也不回收~!
強引用是我們最常見的普通對象引用,只要還有一個強引用指向一個對象,就能表明對象還「活著」,垃圾收集器不會碰這種對象。在Java中最常見的就是強引用,把一個對象賦給一個引用變數,這個引用變數就是一個強引用。當一個對象被強引用變數引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即使該對象以後永遠都不會被用到,JVM也不會回收,因此強引用是造成Java記憶體泄漏的主要原因之一。
對於一個普通的對象,如果沒有其它的引用關係,只要超過了引用的作用於或者顯示地將相應(強)引用賦值為null,一般可以認為就是可以被垃圾收集的了(當然具體回收時機還是要看垃圾回收策略)
強引用小例子:
/**
* 強引用
*/
public class StrongReferenceDemo {
public static void main(String[] args) {
// 這樣定義的默認就是強應用
Object obj1 = new Object();
// 使用第二個引用,指向剛剛創建的Object對象
Object obj2 = obj1;
// 置空
obj1 = null;
// 垃圾回收
System.gc();
System.out.println(obj1);
System.out.println(obj2);
}
}
輸出結果我們能夠發現,即使 obj1 被設置成了null,然後調用gc進行回收,但是也沒有回收實例出來的對象,因為obj2還是能夠指向new Object()這個對象(存儲在堆中),屬於GCRoot可達故而這個對象不會被回收,也就是說垃圾回收器,並沒有將該對象進行垃圾回收
null
java.lang.Object@14ae5a5
軟引用
軟引用是一種相對弱化了一些的引用,需要用Java.lang.ref.SoftReference類來實現,可以讓對象豁免一些垃圾收集,對於只有軟引用的對象來講:
- 當系統記憶體充足時,它不會被回收
- 當系統記憶體不足時,它會被回收
軟引用通常在對記憶體敏感的程式中,比如高速快取就用到了軟引用,記憶體夠用 的時候就保留,不夠用就回收
具體使用
/**
* 軟引用
*/
public class SoftReferenceDemo {
/**
* 記憶體夠用的時候
*/
public static void softRefMemoryEnough() {
// 創建一個強應用
Object o1 = new Object();
// 創建一個軟引用
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
// 手動GC
System.gc();
System.out.println(o1);
System.out.println(softReference.get());
}
/**
* JVM配置,故意產生大對象並配置小的記憶體,讓它的記憶體不夠用了導致OOM,看軟引用的回收情況
* -Xms5m -Xmx5m -XX:+PrintGCDetails
*/
public static void softRefMemoryNoEnough() {
System.out.println("========================");
// 創建一個強應用
Object o1 = new Object();
// 創建一個軟引用
SoftReference<Object> softReference = new SoftReference<>(o1);
System.out.println(o1);
System.out.println(softReference.get());
o1 = null;
// 模擬OOM自動GC
try {
// 創建30M的大對象
byte[] bytes = new byte[30 * 1024 * 1024];
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(o1);
System.out.println(softReference.get());
}
}
public static void main(String[] args) {
softRefMemoryEnough();
softRefMemoryNoEnough();
}
}
我們寫了兩個方法,一個是記憶體夠用的時候,一個是記憶體不夠用的時候
我們首先查看記憶體夠用的時候,首先輸出的是 o1 和 軟引用的 softReference,我們都能夠看到值
然後我們把o1設置為null,執行手動GC後,我們發現softReference的值還存在,說明記憶體充足的時候,軟引用的對象不會被回收
java.lang.Object@14ae5a5
java.lang.Object@14ae5a5
[GC (System.gc()) [PSYoungGen: 1396K->504K(1536K)] 1504K->732K(5632K), 0.0007842 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 504K->0K(1536K)] [ParOldGen: 228K->651K(4096K)] 732K->651K(5632K), [Metaspace: 3480K->3480K(1056768K)], 0.0058450 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
null
java.lang.Object@14ae5a5
下面我們看當記憶體不夠的時候,我們使用了JVM啟動參數配置,給初始化堆記憶體為5M
-Xms5m -Xmx5m -XX:+PrintGCDetails
但是在創建對象的時候,我們創建了一個30M的大對象
// 創建30M的大對象
byte[] bytes = new byte[30 * 1024 * 1024];
這就必然會觸發垃圾回收機制,這也是中間出現的垃圾回收過程,最後看結果我們發現,o1 和 softReference都被回收了,因此說明,軟引用在記憶體不足的時候,會自動回收
java.lang.Object@7f31245a
java.lang.Object@7f31245a
[GC (Allocation Failure) [PSYoungGen: 31K->160K(1536K)] 682K->811K(5632K), 0.0003603 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 160K->96K(1536K)] 811K->747K(5632K), 0.0006385 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 96K->0K(1536K)] [ParOldGen: 651K->646K(4096K)] 747K->646K(5632K), [Metaspace: 3488K->3488K(1056768K)], 0.0067976 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 646K->646K(5632K), 0.0004024 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 646K->627K(4096K)] 646K->627K(5632K), [Metaspace: 3488K->3488K(1056768K)], 0.0065506 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
null
null
弱引用
不管記憶體是否夠,只要有GC操作就會進行回收
弱引用需要用 java.lang.ref.WeakReference
類來實現,它比軟引用生存期更短
對於只有弱引用的對象來說,只要垃圾回收機制一運行,不管JVM的記憶體空間是否足夠,都會回收該對象佔用的空間。
/**
* 弱引用
*/
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(o1);
System.out.println(weakReference.get());
}
}
我們看結果,能夠發現,我們並沒有製造出OOM記憶體溢出,而只是調用了一下GC操作,垃圾回收就把它給收集了
java.lang.Object@14ae5a5
java.lang.Object@14ae5a5
[GC (System.gc()) [PSYoungGen: 5246K->808K(76288K)] 5246K->816K(251392K), 0.0008236 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 808K->0K(76288K)] [ParOldGen: 8K->675K(175104K)] 816K->675K(251392K), [Metaspace: 3494K->3494K(1056768K)], 0.0035953 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
null
null
軟引用和弱引用的使用場景
場景:假如有一個應用需要讀取大量的本地圖片
- 如果每次讀取圖片都從硬碟讀取則會嚴重影響性能
- 如果一次性全部載入到記憶體中,又可能造成記憶體溢出
此時使用軟引用可以解決這個問題
設計思路:使用HashMap來保存圖片的路徑和相應圖片對象關聯的軟引用之間的映射關係,在記憶體不足時,JVM會自動回收這些快取圖片對象所佔的空間,從而有效地避免了OOM的問題
Map<String, SoftReference<String>> imageCache = new HashMap<String, SoftReference<Bitmap>>();
WeakHashMap是什麼?
比如一些常常和底層打交道的,mybatis等,底層都應用到了WeakHashMap
WeakHashMap和HashMap類似,只不過它的Key是使用了弱引用的,也就是說,當執行GC的時候,HashMap中的key會進行回收,下面我們使用例子來測試一下
我們使用了兩個方法,一個是普通的HashMap方法
我們輸入一個Key-Value鍵值對,然後讓它的key置空,然後在查看結果
private static void myHashMap() {
Map<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
第二個是使用了WeakHashMap,完整程式碼如下
/**
* WeakHashMap
*/
public class WeakHashMapDemo {
public static void main(String[] args) {
myHashMap();
System.out.println("==========");
myWeakHashMap();
}
private static void myHashMap() {
Map<Integer, String> map = new HashMap<>();
Integer key = new Integer(1);
String value = "HashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
private static void myWeakHashMap() {
Map<Integer, String> map = new WeakHashMap<>();
Integer key = new Integer(1);
String value = "WeakHashMap";
map.put(key, value);
System.out.println(map);
key = null;
System.gc();
System.out.println(map);
}
}
最後輸出結果為:
{1=HashMap}
{1=HashMap}
==========
{1=WeakHashMap}
{}
從這裡我們看到,對於普通的HashMap來說,key置空並不會影響,HashMap的鍵值對,因為這個屬於強引用,不會被垃圾回收。
但是WeakHashMap,在進行GC操作後,弱引用的就會被回收
虛引用
概念
虛引用又稱為幽靈引用,需要java.lang.ref.PhantomReference
類來實現
顧名思義,就是形同虛設,與其他幾種引用都不同,虛引用並不會決定對象的生命周期。
如果一個對象持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收,它不能單獨使用也不能通過它訪問對象,虛引用必須和引用隊列ReferenceQueue聯合使用。
虛引用的主要作用和跟蹤對象被垃圾回收的狀態,僅僅是提供一種確保對象被finalize以後,做某些事情的機制。
PhantomReference的get方法總是返回null,因此無法訪問對象的引用對象。其意義在於說明一個對象已經進入finalization階段,可以被gc回收,用來實現比finalization機制更靈活的回收操作
換句話說,設置虛引用關聯的唯一目的,就是在這個對象被收集器回收的時候,收到一個系統通知或者後續添加進一步的處理,Java技術允許使用finalize()方法在垃圾收集器將對象從記憶體中清除出去之前,做必要的清理工作
這個就相當於Spring AOP裡面的後置通知
場景
一般用於在回收時候做通知相關操作
引用隊列 ReferenceQueue
軟引用,弱引用,虛引用在回收之前,需要在引用隊列保存一下
我們在初始化的弱引用或者虛引用的時候,可以傳入一個引用隊列
Object o1 = new Object();
// 創建引用隊列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 創建一個弱引用
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
那麼在進行GC回收的時候,弱引用和虛引用的對象都會被回收,但是在回收之前,它會被送至引用隊列中
完整程式碼如下:
/**
* 虛引用
*/
public class PhantomReferenceDemo {
public static void main(String[] args) {
Object o1 = new Object();
// 創建引用隊列
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
// 創建一個弱引用
WeakReference<Object> weakReference = new WeakReference<>(o1, referenceQueue);
// 創建一個弱引用
// PhantomReference<Object> weakReference = new PhantomReference<>(o1, referenceQueue);
System.out.println(o1);
System.out.println(weakReference.get());
// 取隊列中的內容
System.out.println(referenceQueue.poll());
o1 = null;
System.gc();
System.out.println("執行GC操作");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(o1);
System.out.println(weakReference.get());
// 取隊列中的內容
System.out.println(referenceQueue.poll());
}
}
運行結果
java.lang.Object@14ae5a5
java.lang.Object@14ae5a5
null
執行GC操作
null
null
java.lang.ref.WeakReference@7f3124
從這裡我們能看到,在進行垃圾回收後,我們弱引用對象,也被設置成null,但是在隊列中還能夠導出該引用的實例,這就說明在回收之前,該弱引用的實例被放置引用隊列中了,我們可以通過引用隊列進行一些後置操作
GCRoots和四大引用小總結
-
紅色部分在垃圾回收之外,也就是強引用的
-
藍色部分:屬於軟引用,在記憶體不夠的時候,才回收
-
虛引用和弱引用:每次垃圾回收的時候,都會被幹掉,但是它在幹掉之前還會存在引用隊列中,我們可以通過引用隊列進行一些通知機制