四種引用類型的概念
強引用 StrongReference
如果一個對象具有強引用,那么垃圾回收器絕對不會回收它,當內存不足時寧願拋出 OOM 錯誤,使得程序異常停止。
Object object = new Object();
即是一個強引用。
軟引用 SoftReference
如果一個對象只具有軟引用,那么垃圾回收器在內存充足的時候不會回收它,而在內存不足時會回收這些對象。軟引用對象被回收后,Java 虛擬機會把這個軟引用加入到與之關聯的引用隊列中。
弱引用 WeakReference
如果一個對象只具有弱引用,那么垃圾回收器在掃描到該對象時,無論內存充足與否,都會回收該對象的內存。與軟引用相同,弱引用對象被回收后,Java 虛擬機會把這個弱引用加入到與之關聯的引用隊列中。
虛引用 PhantomReference
虛引用並不決定對象生命周期,如果一個對象只具有虛引用,那么它和沒有任何引用一樣,任何時候都可能被回收。虛引用主要用來跟蹤對象被垃圾回收器回收的活動。與軟引用和弱引用不同的是,虛引用必須關聯一個引用隊列。
當垃圾回收器准備回收一個對象之前,如果發現它還具有虛引用,就會在對象回收前把這個虛引用加入到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否加入了虛引用,來了解被引用的對象是否將要被回收,那么就可以在其被回收之前采取必要的行動。
軟引用構建敏感數據的緩存
軟引用非常適合於創建緩存。當系統內存不足的時候,緩存中的內容是可以被釋放的。
例如,一個程序用來處理用戶提供的圖片。如果將所有圖片讀入內存,這樣雖然可以很快的打開圖片,但內存空間使用巨大,一些使用較少的圖片浪費內存空間,需要手動從內存中移除。如果每次打開圖片都從磁盤文件中讀取到內存再顯示出來,雖然內存占用較少,但一些經常使用的圖片每次打開都要訪問磁盤,代價巨大。這個時候就可以用軟引用構建緩存。
// 獲取對象並緩存
Object object = new Object();
SoftReference softRef = new SoftReference(object);
// 從軟引用中獲取對象
Object object = (Object) softRef.get();
if (object == null){
// 當軟引用被回收后重新獲取對象
object = new Object();
}
弱引用解決內存泄露問題
雖然 Java 有垃圾回收機制,但是只要是自己管理的內存,就應該警惕內存泄露的問題,例如對象池、緩存中的過期對象都有可能引發內存泄露的問題。用 WeakHashMap 來作為緩存的容器可以有效解決這一問題。WeakHashMap 和 HashMap 幾乎一樣,唯一的區別就是它的鍵使用弱引用。當 WeakHashMap 的鍵標記為過期時,這個鍵對應的條目就會自動被移除。這就避免了內存泄漏問題。
另外在 Android 中常常用到 Handler 消息處理機制。當使用內部類(包括匿名內部類)來創建 Handler 時,Handler 對象會隱式的持有一個其外部類對象(通常是一個 Activity)的引用。而 Handler 通常會開啟一個耗時的工作線程(例如下載獲取數據),工作線程完成任務后,通過消息機制通知 Handler 在主線程做相應的處理。如果在工作線程執行的過程中關閉了 Activity,Activity 應該在 GC 檢查時被回收掉。但因為 Activity 被 Handler 持有引用,Handler 又被 Message 持有引用,Message 被 MessageQueue 持有引用,MessageQueue 被 Looper 持有引用,而 Looper 是線程本地變量,線程不銷毀,它就不會被銷毀。這樣一個引用鏈,導致 Activity 無法被回收,造成內存泄漏。
1. 通過程序邏輯控制防止內存泄漏
比如在 Activity 關閉時停掉工作線程,移除消息隊列中的消息對象,這樣切斷了 Activity 的引用,就可以正常的被回收了。
2. 將 Handler 聲明成靜態內部類
靜態類不持有外部類的對象,所以 Activity 可以被正常的回收。但這個時候 Handler 無法操作 Activity 中的對象了,所以這個時候需要增加一個對 Activity 弱引用。代碼如下:
static class MyHandler extends Handler {
private WeakReference<Activity> reference;
public MyHandler(Activity activity) {
// 持有 Activity 的弱引用
reference = new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
Activity activity = reference.get();
if (activity != null && !activity.isFinishing()) {
switch (msg.what) {
// 處理消息
}
}
}
}