使用Handler容易產生的記憶體洩露以及介紹下Java的4種引用
最近時間都利用的不太好,都是到下午才開始學習或者做事,一上午都吹B或者XXX用掉了。。。不太好,這裡督促下自己不要再懶惰,哈哈!!
廢話不多說,今天來講下一個“經常”遇到的一個記憶體洩露的情況來引出想提的Java的4種引用方式
在舉例子之前先將一些基礎的知識,不然基礎薄弱的同學還會不理解什麼的。(當然,我會講的很粗略,但是你一定能看懂)
通常Java分配記憶體有三種策略,分別為 靜態儲存區,棧,堆。
靜態儲存區: 平時那些static的變數啊,方法啊都在這裡面,並且在程式整個執行期間都存在。
棧:我們執行的那些方法所產生的物件都在這裡面,方法結束這些東西就會被釋放掉。
堆:我們平時 new出來的那些物件都會在這裡面。
那如何區分堆,棧呢?
區域性變數的基本資料型別和引用儲存於棧中,引用的物件實體儲存於堆中。—— 因為它們屬於方法中的變數,生命週期隨方法而結束。
成員變數全部儲存於堆中(包括基本資料型別,引用和引用的物件實體)—— 因為它們屬於類,類物件終究是要被new出來使用的。
那接下來我們來看下例子
Handler 造成的記憶體洩漏
為什麼會出現?
Handler 屬於 TLS(Thread Local Storage) 變數, 生命週期和 Activity 是不一致的。所以很有可能我們的Activity的邏輯早就結束了,但是Handler持有Activity的引用導致Activity的資源無法回收。
可以看下這個簡單的例子:
public classWjjActivityextendsActivity {
private final Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// 你的業務行為
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 強行延遲操作
myHandler.postDelayed(new Runnable() {
@Override
public void run() { /* ... */ }
}, 1000 * 60 * 20);
finish();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
myHandler 將其 push 進了訊息佇列 MessageQueue 裡。 當該 Activity 被 finish() 掉時,延遲執行任務的 Message 還會繼續存在於主執行緒中,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了 從而造成記憶體洩漏(因 Handler 為非靜態內部類,它會持有外部類的引用)。
那我們可以在Handler申明的時候加 static,讓他存活期跟 Activity 的生命週期就無關了。
然後我又找到了一個更好的解決方法,就是通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進去。
public classSampleActivityextendsActivity {
/**
* Instances of static inner classes do not hold an implicit
* reference to their outer class.
*/
private static classMyHandlerextendsHandler {
private final WeakReference<SampleActivity> mActivity;
public MyHandler(SampleActivity activity) {
mActivity = new WeakReference<SampleActivity>(activity);
}
@Override
public void handleMessage(Message msg) {
SampleActivity activity = mActivity.get();
if (activity != null) {
// ...
}
}
}
private final MyHandler mHandler = new MyHandler(this);
/**
* Instances of anonymous classes do not hold an implicit
* reference to their outer class when they are "static".
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() { /* ... */ }
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Post a message and delay its execution for 10 minutes.
mHandler.postDelayed(sRunnable, 1000 * 60 * 10);
// Go back to the previous Activity.
finish();
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
這樣如果Activity已經不在了,也就不做這些事了。
上面已經用到了弱飲用,那麼和強引用又有什麼區別呢?就是接下來要講的。
java的引用有4種分別是 強引用(StrongReference),軟引用(SoftReference),弱引用(WeakReference),虛引用(PhantomReference)。
強引用(StrongReference)
強引用是使用最普遍的引用。如果一個物件具有強引用,那垃圾回收器絕不會回收它。 就是我們平時 new 的那些物件(這裡不包括區域性變數,別誤解)
像這樣
Apple a=new Apple();
- 1
如果是方法內的區域性變數,在方法結束前你也不需要做把他設定為null的操作,讓GC來消除他。顯式地設定a為null,或超出物件的生命週期範圍,則gc認為該物件不存在引用,這時就可以回收這個物件
像list之類的因為 你通常是 List list =new ArrayList<>();出來了的,所以如果不用了,建議手動clear()下,而不是以為list.get(x)為null了他就會被回收。
軟引用(SoftReference)
如果一個物件只具有軟引用,則記憶體空間足夠,垃圾回收器就不會回收它;如果記憶體空間不足了,就會回收這些物件的記憶體。
String v=new String("wjj"); // 強引用
SoftReference<String> softRef=new SoftReference<String>(v); // 軟引用
- 1
- 2
- 3
那麼在記憶體吃緊的時候softRef就會被回收。
使用場景: 我有一個Activity,他有很多圖片,圖片都在map裡快取,我切出去再back回來如果不用快取,每次IO操作去讀sd卡,但是有了快取我就可以迅速的載入,提升使用者的體驗。如果記憶體吃緊被回收了,那也只能去重新載入了(強烈抗議刻意不讓系統回收這些快取資料)
弱引用(WeakReference)
在垃圾回收器執行緒掃描它所管轄的記憶體區域的過程中,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體。
和軟引用的區別就是,一個記憶體不足才回收,一個看到就回收!
用法跟上面幾乎一樣,就不貼程式碼了。
應用推薦: 如果這個物件是偶爾的使用,並且希望在使用時隨時就能獲取到,但又不想影響此物件的垃圾收集,那麼你應該用 Weak Reference 來記住此物件。
虛引用(PhantomReference)
如果一個物件僅持有虛引用,那麼它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收。
大致的概念如下
因為一些個人原因,後面幾天我都會處於休假狀態,整理的東西會停更幾天,恢復工作狀態後我會補上吧,提前預祝大家 5.1愉快!
部分知識源於網上,Tx for 簫鑑哥