簡介
說明
本文用示例介紹Java中的引用類型的差別及使用。
從JDK 1.2版本開始,對象的引用被劃分為4種級别,由高到低依次為:強引用、軟引用、弱引用和虛引用。
本内容也是Java後端面試常見的問題。
為什麼分為四種引用類型?
Java設計這四種引用的主要目的有兩個:
-
- 可以讓程式員通過代碼的方式來決定某個對象的生命周期
- 有利于垃圾回收
引用類型簡介
-
- 強引用
-
- 強引用是使用最普遍的引用,我們寫的代碼,99.9999%都是強引用
- 隻要某個對象有強引用與之關聯,這個對象永遠不會被回收,即使記憶體不足,JVM甯願抛出OOM,也不會去回收。
- 軟引用
-
- 隻有在記憶體不足時,JVM才會回收該對象。
- 當記憶體不足時,會觸發JVM的GC,如果GC後,記憶體還是不足,就會把軟引用包裹的對象給幹掉。
- 弱引用
- 不管記憶體是否足夠,隻要發生GC,弱引用就會被回收。
- 虛引用
-
- 無法通過虛引用來擷取對一個對象的真實引用。
- 虛引用必須與ReferenceQueue一起使用。當GC準備回收一個對象時,如果發現它還有虛引用,就會在回收之前,把這個虛引用加入到與之關聯的ReferenceQueue中。
強引用
簡介
強引用是使用最普遍的引用,我們寫的代碼,99.9999%都是強引用。
隻要某個對象有強引用與之關聯,這個對象永遠不會被回收,即使記憶體不足,JVM甯願抛出OOM,也不會去回收。
強引用寫法:Object o = new Object();
讓強引用的對象被回收的寫法:o = null;
代碼執行個體
用代碼展示回收強引用的對象。
finalize方法在垃圾回收的時候會被調用。
package com.example.a;
class User{
@Override
protected void finalize() {
System.out.println("User 被回收了");
}
}
public class Demo {
public static void main(String[] args) {
User user = new User();
user = null;
System.gc();
}
}
結果
User 被回收了
總結
-
- 可以看到資源被回收了。
- 在實際開發中,不要重寫finalize方法
- 在實際開發中,若看到有對象被手動指派為null,很大可能就是為了“特意提醒”JVM這塊資源可以進行垃圾回收了。
軟引用
簡介
說明
隻有在記憶體不足時,JVM才會回收該對象。
當記憶體不足時,會觸發JVM的GC,如果GC後,記憶體還是不足,就會把軟引用包裹的對象給幹掉。
應用場景:
軟引用主要用于緩存:當記憶體足夠時,可以正常的拿到緩存,當記憶體不夠時,就會先幹掉緩存,不至于馬上抛出OOM。
比如:浏覽器的後退按鈕。按後退時,這個後退時顯示的網頁内容是重新進行請求還是從緩存中取出呢?這就要看具體的實作政策了。
-
- 如果一個網頁在浏覽結束時就進行内容的回收,則按後退檢視前面浏覽過的頁面時,需要重新建構;
- 如果将浏覽過的網頁存儲到記憶體中會造成記憶體的大量浪費,甚至會造成記憶體溢出。
這時候就可以使用軟引用,很好的解決了實際的問題:
// 擷取浏覽器對象進行浏覽
Browser browser = new Browser();
// 從背景程式加載浏覽頁面
BrowserPage page = browser.getPage();
// 将浏覽完畢的頁面置為軟引用
SoftReference softReference = new SoftReference(page);
// 回退或者再次浏覽此頁面時
if(softReference.get() != null) {
// 記憶體充足,還沒有被回收器回收,直接擷取緩存
page = softReference.get();
} else {
// 記憶體不足,軟引用的對象已經回收
page = browser.getPage();
// 重新建構軟引用
softReference = new SoftReference(page);
}
代碼執行個體
代碼寫法
建立一個軟引用:
SoftReference<User> softReference = new SoftReference<User>(new User());
從軟引用對象獲得包裹的對象:
User User = softReference.get();
System.out.println(User);
執行個體:用代碼展示回收軟引用的對象
定義一個軟引用對象,裡面包裹了byte[],byte[]占用了10M,然後又建立了10Mbyte[]。
package com.example.a;
import java.lang.ref.SoftReference;
public class Demo {
public static void main(String[] args) {
SoftReference<byte[]> softReference = new SoftReference<byte[]>(new byte[1024*1024*10]);
System.out.println(softReference.get());
System.gc();
System.out.println(softReference.get());
byte[] bytes = new byte[1024 * 1024 * 10];
System.out.println(softReference.get());
}
}
執行結果
[B@1b6d3586
[B@1b6d3586
[B@1b6d3586
現在我們手動制造記憶體不足:設定最大堆大小為15M:-Xmx15m
法1:先編譯程式,再java -Xmx15m xxx
法2:Idea指定參數
執行結果
[B@1b6d3586
[B@1b6d3586
null
結論
手動GC後,軟引用對象包裹的byte[]還活的好好的,但是當我們建立了一個10M的byte[]後,最大堆記憶體不夠了,是以JVM把軟引用對象包裹的byte[]給幹掉了。
弱引用
簡介
說明
不管記憶體是否足夠,隻要發生GC,弱引用就會被回收。
使用場景
若引用主要用于緩存:當沒有發生GC時,可以正常的拿到緩存,當發生GC時,就會先幹掉緩存,不至于馬上抛出OOM。
JDK裡對弱引用的使用:ThreadLocal、WeakHashMap。
如果一個對象是偶爾(很少)的使用,并且希望在使用時随時就能擷取到,但又不想影響此對象的垃圾收集,就可以使用弱引用。
代碼執行個體
代碼寫法
弱引用的使用和軟引用類似,隻是關鍵字變成了WeakReference
建立一個軟引用:
WeakReference<User> weakReference = new WeakReference<User>(new User());
從軟引用對象獲得包裹的對象:
User User = weakReference.get();
System.out.println(User);
執行個體:用代碼展示回收弱引用的對象
package com.example.a;
import java.lang.ref.WeakReference;
public class Demo {
public static void main(String[] args) {
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
}
}
結果
[B@1b6d3586
null
結論
不管記憶體是否足夠,隻要發生GC,弱引用就會被回收。
虛引用
簡介
說明
無法通過虛引用來擷取對一個對象的真實引用。
虛引用必須與ReferenceQueue一起使用。當GC準備回收一個對象時,如果發現它還有虛引用,就會在回收之前,把這個虛引用加入到與之關聯的ReferenceQueue中。
使用場景
虛引用主要用來跟蹤對象被垃圾回收器回收的活動。
程式可以通過判斷引用隊列中是否已經加入了虛引用,來了解被引用的對象是否将要進行垃圾回收。如果程式發現某個虛引用已經被加入到引用隊列,那麼就可以在所引用的對象的記憶體被回收之前采取必要的行動。
NIO運用了虛引用管理堆外記憶體。
代碼執行個體
簡單執行個體
package com.example.a;
import java.lang.ref.PhantomReference;
import java.lang.ref.ReferenceQueue;
public class Demo {
public static void main(String[] args) {
ReferenceQueue queue = new ReferenceQueue();
PhantomReference<byte[]> reference = new PhantomReference<byte[]>(new byte[1], queue);
System.out.println(reference.get());
}
}
結果
null
看源碼:竟然直接傳回了null。
這就是虛引用特點之一:無法通過虛引用來擷取對一個對象的真實引用。
執行個體:用代碼展示回收弱引用的對象
package com.example.a;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
class User {
@Override
protected void finalize() {
System.out.println("User 被回收了");
}
}
public class Demo {
public static void main(String[] args) {
ReferenceQueue queue = new ReferenceQueue();
List<byte[]> bytes = new ArrayList<>();
PhantomReference<User> phantomReference = new PhantomReference<User>(new User(), queue);
new Thread(() -> {
for (int i = 0; i < 100; i++) {
bytes.add(new byte[1024 * 1024]);
}
}).start();
new Thread(() -> {
while (true) {
Reference poll = queue.poll();
if (poll != null) {
System.out.println("虛引用被回收了:" + poll);
}
}
}).start();
Scanner scanner = new Scanner(System.in);
scanner.hasNext();
}
}
我們手動制造記憶體不足:設定最大堆大小為15M:-Xmx15m
結果:
User 被回收了
Exception in thread "Thread-0" java.lang.OutOfMemoryError: Java heap space
at com.example.a.Demo.lambda$main$0(Demo.java:24)
at com.example.a.Demo$$Lambda$1/2003749087.run(Unknown Source)
at java.lang.Thread.run(Thread.java:748)
虛引用被回收了:java.lang.ref.PhantomReference@2c4772fc