Java中提供了4個級别的引用: 強引用、軟引用、弱引用和虛引用, 這四個引用定義在包`java.lang.ref`下. 本篇博文通過代碼詳細示範不同引用類型的作用, 如有不當之處, 歡迎評論區交流.
目錄
- 1 強引用 (Final Reference)
- 2 軟引用 (Soft Reference)
- 2.1 案例1: 軟引用的垃圾回收
- 2.2 案例2: 軟引用緩存的使用
- 2.3 軟引用的應用場景
- 3 弱引用 (Weak Reference)
- 4 虛引用 (Phantom Reference)
- 參考資料
- 版權聲明
版權聲明: 本文為轉載文章, 轉載時有适量修改. 再次轉載時請附上原文出處連結和本聲明.
原文連結: https://blog.csdn.net/u013256816/article/details/50907595
Java中提供了4個級别的引用: 強引用、軟引用、弱引用和虛引用, 這四個引用定義在包
java.lang.ref
下:
強引用就是指在程式代碼中普遍存在的、類似于
Object obj = new Object()
的引用, 隻要強引用還存在, 垃圾收集器就不會去回收這些被引用的對象.
強引用有以下三個特點:
- 強引用可以直接通路目标對象;
- 強引用鎖指向的對象在任何時候都不會被系統回收 ——JVM甯願抛出OOM異常也不回收強引用所指向的對象;
- 強引用可能導緻記憶體洩露, 比如List中添加了new出來的對象, List失去被回收之後, 其内部的對象不能被通路、卻又不會被回收的現象.
FinalReference類的全部定義如下:
package java.lang.ref;
/**
* Final references, used to implement finalization
*/
class FinalReference<T> extends Reference<T> {
public FinalReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
FinalReference隻有一個構造函數: 根據給定對象的引用和引用隊列構造一個強引用.
軟引用用來描述一些還有用但并非必須的對象: 被軟引用關聯着的對象, 如果記憶體充足, 則垃圾回收器不會回收該對象;
如果記憶體不夠用, 就會回收這些對象.
在系統将要發生之前, JVM會把被軟引用關聯着的對象列進回收範圍, 并進行第二次回收. 如果這次回收之後記憶體仍然不夠用, 系統才會抛出
OutOfMemoryError
.
OutOfMemoryError
從JDK 1.2開始提供了SoftReference類來實作軟引用: 與一個引用隊列 (ReferenceQueue) 聯合使用實作記憶體敏感的高速緩存 —— 如果軟引用所引用的對象被垃圾回收器回收, JVM就會把這個軟引用加入到與之關聯的引用隊列中.
package com.healchow.java.detail;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
public class SoftRefTest {
private static ReferenceQueue<MyObject> softQueue = new ReferenceQueue<>();
private static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
private static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) softQueue.remove();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (obj != null) {
System.out.println("Object for SoftReference is " + obj.get());
}
}
}
public static void main(String[] args) {
MyObject obj = new MyObject();
SoftReference<MyObject> softRef = new SoftReference<>(obj, softQueue);
new Thread(new CheckRefQueue()).start();
// 删除強引用, 否則obj不會被回收
obj = null;
System.gc();
System.out.println("After GC: Soft Get = " + softRef.get());
System.out.println("嘗試配置設定大塊記憶體...");
byte[] b = new byte[5 * 1024 * 725];
System.out.println("After new byte[]: Soft Get = " + softRef.get());
System.gc();
}
}
(1) 測試方法1:
在運作測試主方法時設定VM參數:
-Xmx5M
, 也就是指定該程式的Java Heap最大為5MB, 運作結果為:
After GC: Soft Get = I am MyObject
嘗試配置設定大塊記憶體...
After new byte[]: Soft Get = I am MyObject
MyObject's finalize called
Object for SoftReference is null
案例代碼解釋:
① 首先構造MyObject對象, 并将其指派給object變量, 構成強引用.
② 然後使用SoftReference構造這個MyObject對象的軟引用softRef, 并注冊到softQueue引用隊列 —— 當softRef被回收時, 會被加入softQueue隊列.
③ 設定
obj = null
, 删除這個強引用, 這時系統内對MyObject對象的引用隻剩下軟引用.
④ 顯示調用GC, 通過軟引用的get()方法擷取MyObject對象的引用, 發現對象并未被回收, 這說明GC在記憶體充足的情況下, 不會回收軟引用對象.
⑤ 接着請求一塊大的堆空間
(要多次調整使得垃圾回收工作能順利進行、線程能順利退出), 這個操作會使系統堆記憶體使用緊張, 進而産生新一輪的GC. 在這次GC後,
5*1024*725
不再傳回MyObject對象, 而是傳回null —— 說明在系統記憶體緊張的情況下, 軟引用被回收. 軟引用被回收時, 會被加入注冊的引用隊列, 此時引用隊列中有了元素, 開辟的多線程中
softRef.get()
不再阻塞, 是以程式得以成功退出.
softQueue.remove()
如果将上面案例中的數組再改大點, 比如
5*1024*1024
, 就會抛出OOM異常:
After GC: Soft Get = I am MyObject
嘗試配置設定大塊記憶體...
MyObject's finalize called
Exception in thread "main" Object for SoftReference is null
java.lang.OutOfMemoryError: Java heap space
at com.healchow.java.detail.SoftRefTest.main(SoftRefTest.java:56)
(2) 測試方法2:
-Xmx5M -XX:PrintGCDetails
, 列印出GC的日志資訊(關于GC日志可以檢視《Java堆記憶體http://blog.csdn.net/u013256816/article/details/50764532》), 運作結果為:
[GC (Allocation Failure) [PSYoungGen: 1024K->480K(1536K)] 1024K->480K(5632K), 0.0013139 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (System.gc()) [PSYoungGen: 1383K->480K(1536K)] 1383K->544K(5632K), 0.0011186 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 480K->0K(1536K)] [ParOldGen: 64K->504K(4096K)] 544K->504K(5632K), [Metaspace: 3316K->3316K(1056768K)], 0.0044642 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
After GC: Soft Get = I am MyObject
嘗試配置設定大塊記憶體...
[GC (Allocation Failure) [PSYoungGen: 38K->64K(1536K)] 542K->568K(5632K), 0.0009263 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 64K->32K(1536K)] 568K->536K(5632K), 0.0011345 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
[Full GC (Allocation Failure) [PSYoungGen: 32K->0K(1536K)] [ParOldGen: 504K->504K(4096K)] 536K->504K(5632K), [Metaspace: 3317K->3317K(1056768K)], 0.0038031 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] 504K->504K(5632K), 0.0007999 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(1536K)] [ParOldGen: 504K->486K(4096K)] 504K->486K(5632K), [Metaspace: 3317K->3317K(1056768K)], 0.0037241 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
MyObject's finalize called
Object for SoftReference is null
After new byte[]: Soft Get = null
[Full GC (System.gc()) [PSYoungGen: 59K->0K(1536K)] [ParOldGen: 4086K->4083K(4096K)] 4145K->4083K(5632K), [Metaspace: 3317K->3317K(1056768K)], 0.0036086 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap
PSYoungGen total 1536K, used 10K [0x00000007bfe00000, 0x00000007c0000000, 0x00000007c0000000)
eden space 1024K, 1% used [0x00000007bfe00000,0x00000007bfe02a68,0x00000007bff00000)
from space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
to space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
ParOldGen total 4096K, used 4083K [0x00000007bfa00000, 0x00000007bfe00000, 0x00000007bfe00000)
object space 4096K, 99% used [0x00000007bfa00000,0x00000007bfdfcfc0,0x00000007bfe00000)
Metaspace used 3324K, capacity 4564K, committed 4864K, reserved 1056768K
class space used 366K, capacity 388K, committed 512K, reserved 1048576K
public class BitMapManager {
private Map<String, SoftReference<Bitmap>> imageCache = new HashMap<>();
// 儲存Bitmap的軟引用到HashMap
public void saveBitmapToCache(String path) {
// 強引用的Bitmap對象
Bitmap bitmap = BitmapFactory.decodeFile(path);
// 軟引用的Bitmap對象
SoftReference<Bitmap> softBitmap = new SoftReference<Bitmap>(bitmap);
// 添加該對象到Map中使其緩存
imageCache.put(path, softBitmap);
// 使用完後手動将位圖對象置null
bitmap = null;
}
public Bitmap getBitmapByPath(String path) {
// 從緩存中取軟引用的Bitmap對象
SoftReference<Bitmap> softBitmap = imageCache.get(path);
// 判斷是否存在軟引用
if (softBitmap == null) {
return null;
}
// 取出Bitmap對象,如果由于記憶體不足Bitmap被回收,将取得空
Bitmap bitmap = softBitmap.get();
return bitmap;
}
}
軟引用主要用于記憶體敏感的高速緩存, 在Android系統中經常用到. 大多數 Android應用都會用到大量的圖檔. 由于讀取檔案需要硬體操作, 速度較慢, 是以考慮将圖檔緩存起來, 需要的時候直接從記憶體中讀取.
但由于圖檔占用記憶體空間比較大, 緩存很多圖檔就可能容易發生OutOfMemoryError, 這時我們可以考慮使用軟引用技術來避免這個問題發生.
SoftReference可以解決OOM的問題: 每一個對象通過軟引用進行執行個體化, 這個對象就以cache的形式儲存起來, 再次調用這個對象時, 就可以直接通過軟引用中的
get()
方法得到對象中的資源資料. 當記憶體将要發生OOM的時候, GC會迅速把所有的軟引用清除, 防止OOM發生.
弱引用用來描述非必須的對象, 它的強度比軟引用更弱, 被弱引用關聯的對象隻能生存到下一次垃圾收集發生之前. 當垃圾收集器工作時, 無論目前記憶體是否足夠, 都會回收掉隻被弱引用關聯的對象. 一旦一個弱引用對象被垃圾回收器回收, 便會加入到一個注冊引用隊列中.
我們略微修改一下上一節案例1的代碼:
package com.healchow.java.detail;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
public class WeakRefTest {
private static ReferenceQueue<MyObject> weakQueue = new ReferenceQueue<>();
private static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
private static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>)weakQueue.remove();
}
catch (InterruptedException e) {
e.printStackTrace();
}
if(obj != null) {
System.out.println("删除的弱引用為: " + obj);
System.out.println("擷取弱引用的對象 obj.get() 為: " + obj.get());
}
}
}
public static void main(String[] args) {
MyObject object = new MyObject();
Reference<MyObject> weakRef = new WeakReference<>(object,weakQueue);
System.out.println("建立的弱引用為: " + weakRef);
new Thread(new CheckRefQueue()).start();
object = null;
System.out.println("Before GC: Weak Get = " + weakRef.get());
System.gc();
System.out.println("After GC: Weak Get = " + weakRef.get());
}
}
(1) 示範方法 —— 不修改JVM參數:
運作結果為:
建立的弱引用為: java.lang.ref.WeakReference@6f94fa3e
Before GC: Weak Get = I am MyObject
After GC: Weak Get = null
删除的弱引用 obj 為: java.lang.ref.WeakReference@6f94fa3e
但是擷取弱引用的對象 obj.get() 為: null
MyObject's finalize called
可以看到:
在GC之前, 弱引用對象并未被垃圾回收器發現, 是以通過
weakRef.get()
可以擷取對應的對象引用.
但是隻要進行垃圾回收, 弱引用就會被發現, 并立即被回收, 并加入注冊引用隊列中. 此時再試圖通過
擷取對象的引用就會失敗.
weakRef.get()
(2) 弱引用的使用場景:
弱引用的使用場景可參考 java.util.WeakHashMap
軟引用、弱引用都非常适合來儲存那些可有可無的緩存資料. 系統記憶體不足時, 這些緩存資料會被回收, 就不會導緻記憶體溢出. 而當記憶體資源充足時, 這些緩存資料又可以存在相當長的時間, 進而提高系統的響應速度用.
虛引用也稱為幽靈引用或者幻影引用, 它是最弱的一種引用關系. 一個持有虛引用的對象, 和沒有引用幾乎是一樣的 —— 随時都有可能被垃圾回收器回收.
當試圖通過虛引用的
get()
方法取得強引用時, 總是會失敗.
并且虛引用必須和引用隊列一起使用, 它的作用在于跟蹤垃圾回收過程.
虛引用中
get()
方法永遠傳回null, 其實作如下:
public T get() {
return null;
}
我們再來修改第2節案例1的代碼:
package com.healchow.java.detail;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.TimeUnit;
public class PhantomRefTest {
private static ReferenceQueue<MyObject> phantomQueue = new ReferenceQueue<>();
private static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
private static class CheckRefQueue implements Runnable {
Reference<MyObject> obj = null;
@Override
public void run() {
try {
obj = (Reference<MyObject>) phantomQueue.remove();
System.out.println("删除的虛引用 obj 為: " + obj);
System.out.println("但是擷取虛引用的對象 obj.get() 為: " + obj.get());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
Reference<MyObject> phantomRef = new PhantomReference<>(object, phantomQueue);
System.out.println("建立的虛引用為:" + phantomRef);
new Thread(new CheckRefQueue()).start();
object = null;
TimeUnit.SECONDS.sleep(1);
int i = 1;
while (true) {
System.out.println("第" + i++ + "次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
建立的虛引用為:java.lang.ref.PhantomReference@6f94fa3e
第1次gc
MyObject's finalize called
第2次gc
删除的虛引用 obj 為: java.lang.ref.PhantomReference@6f94fa3e
但是擷取虛引用的對象 obj.get() 為: null
在經過一次GC之後, 系統找到了垃圾對象, 并調用finalize()方法回收記憶體, 但沒有立即将虛引用對象加入回收隊列.
第二次GC時, 該對象真正被GC清除, 此時虛引用對象被加入虛引用隊列.
(2) 虛引用的使用場景:
虛引用的最大作用在于跟蹤對象回收, 清理被銷毀對象的相關資源.
通常當對象不被使用時, 重載該對象的類的
finalize()
方法可以回收對象的資源. 但是如果使用不慎, 會使得對象複活. 比如這樣重寫
finalize()
方法:
public class Test {
private static Test obj;
@Override
protected void finalize() throws Throwable {
super.finalize();
obj = this;
}
}
建立Test對象:
obj = new Test();
, 然後令
obj = null;
, 之後調用
System.gc()
企圖銷毀該對象.
但是很抱歉, 不管你調用多少次
System.gc()
都沒有用, 除非再次
obj = null;
才能回收對象.
原因: JVM對每一個對象最多隻執行一次被重寫的
finalize()
方法, 示例代碼中, 在
super.finalize()
之後又對obj進行了指派, 使得obj又複活了, 它重寫的finalize()方法不會被調用第二次.
(3) 通過虛引用清理對象
上面的小片段說明重寫
finalize()
方法并不是很靠譜, 我們可以使用虛引用來清理對象所占用的資源. 修改代碼如下:
package com.healchow.java.detail;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class PhantomRefTest2 {
private static ReferenceQueue<MyObject> phantomQueue = new ReferenceQueue<>();
private static Map<Reference<MyObject>, String> resourceMap = new HashMap<>();
private static class MyObject {
@Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("MyObject's finalize called");
}
@Override
public String toString() {
return "I am MyObject";
}
}
private static class CheckRefQueue implements Runnable {
Reference<MyObject> refObj = null;
@Override
public void run() {
try {
refObj = (Reference<MyObject>) phantomQueue.remove();
// 從資源Map中移除弱引用對象, 即手動釋放資源
Object value = resourceMap.get(refObj);
System.out.println("clean resource: " + value);
resourceMap.remove(refObj);
System.out.println("删除的虛引用為: " + refObj);
System.out.println("擷取虛引用的對象 obj.get() 為: " + refObj.get());
System.exit(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyObject object = new MyObject();
Reference<MyObject> phantomRef = new PhantomReference<>(object, phantomQueue);
System.out.println("建立的虛引用為: " + phantomRef);
new Thread(new CheckRefQueue()).start();
// 将建立的虛引用對象存入資源Map
resourceMap.put(phantomRef, "Some Resources");
object = null;
TimeUnit.SECONDS.sleep(1);
int i = 1;
while (true) {
System.out.println("第" + i++ + "次gc");
System.gc();
TimeUnit.SECONDS.sleep(1);
}
}
}
運作結果:
建立的虛引用為: java.lang.ref.PhantomReference@6f94fa3e
第1次gc
MyObject's finalize called
第2次gc
clean resource: Some Resources
删除的虛引用 obj 為: java.lang.ref.PhantomReference@6f94fa3e
但是擷取虛引用的對象 obj.get() 為: null
《Java程式性能優化——讓你的Java程式更快、更穩定》葛一鳴 等編著。
《Java堆記憶體http://blog.csdn.net/u013256816/article/details/50764532》
本文版權歸原作者所有,如有侵權,請聯系部落客,定當立即删除。
感謝閱讀,公衆号 「瘦風的南牆」 ,手機端閱讀更佳,還有其他福利和心得輸出,歡迎掃碼關注🤝