內(nèi)存泄漏情況分類
- 1、單例模式導(dǎo)致內(nèi)存泄漏
- 2、靜態(tài)變量導(dǎo)致內(nèi)存泄漏
- 3日熬、非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄漏
- 4、未取消注冊或未取消回調(diào)導(dǎo)致內(nèi)存泄
- 5肾胯、Timer 和 TimerTask 導(dǎo)致內(nèi)存泄露
- 6竖席、集合中的對象未清理造成內(nèi)存泄露
- 7、資源未關(guān)閉或釋放導(dǎo)致內(nèi)存泄露
- 8敬肚、屬性動畫造成內(nèi)存泄露
- 9毕荐、WebView 造成內(nèi)存泄漏
1、 單例模式導(dǎo)致內(nèi)存泄漏
單例模式在 Android 開發(fā)中會經(jīng)常用到帘皿,但是如果使用不當(dāng)就會導(dǎo)致內(nèi)存泄露东跪。因為單例的靜態(tài) 特性使得它的生命周期同應(yīng)用的生命周期一樣長,如果一個對象已經(jīng)沒有用處了鹰溜,但是單例還持 有它的引用虽填,那么在整個應(yīng)用程序的生命周期它都不能正常被回收,從而導(dǎo)致內(nèi)存泄露曹动。
- 如下代碼所示:
public static DBManage getInstance(Context context) {
if (sInstance == null) {
sInstance = new DBManage(context);
}
return sInstance;
}
<font size=5 color=#0099ff face="仿宋">
如以上代碼斋日,如果使用該單例模式在activity中,傳入當(dāng)前activity作為contenxt墓陈,那么就會很容易出現(xiàn)內(nèi)存泄漏恶守,
因為
</font>
2、靜態(tài)變量導(dǎo)致的內(nèi)存泄漏
靜態(tài)變量存儲在方法區(qū)贡必,他的生命周期從類加載開始創(chuàng)建兔港,到整個進(jìn)程結(jié)束,一旦靜態(tài)變量初始化之后仔拟,他所持有的引用只有等到進(jìn)程結(jié)束才會被釋放
- 如以下示例:
public class MainActivity extends AppCompatActivity {
private static Info sInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sInfo == null) {
sInfo = new Info(this);
}
}
}
<font size=5 color=#0099ff face="仿宋">
Info 作為 MainActivity 的靜態(tài)成員衫樊,并且持有activity的引用,但是sInfo作為靜態(tài)變量利花,生命周期肯定比activity要長科侈,當(dāng)activity退出后,Info仍然持有activity的引用炒事,故導(dǎo)致activity不能被GC回收臀栈;
靜態(tài)持有很多時候都是因為生命周期和引用者不一致導(dǎo)致內(nèi)存泄漏,所以我們在新建靜態(tài)持有的時候多考慮一下各個引用與被引用者的生命周期,盡量少的使用靜態(tài)持有的變量,以免發(fā)生內(nèi)存泄漏,如果必須用到的時候我們可以在使用完之后將變量手動置null
使其不在持有引用臀稚。
</font>
3崭闲、非靜態(tài)內(nèi)部類導(dǎo)致的內(nèi)存泄漏
非靜態(tài)內(nèi)部類(包含匿名內(nèi)部類)默認(rèn)就會持有外部類的引用肋联,當(dāng)非靜態(tài)內(nèi)部類的對象的生命周期比外部內(nèi)生命周期長時就會導(dǎo)致內(nèi)存泄漏
- 如下示例:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
start();
}
private void start() {
Message msg = Message.obtain();
msg.what = 1;
mHandler.sendMessage(msg);
}
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
// 做相應(yīng)邏輯
}
}
};
}
也許有人會說,mHandler 并未作為靜態(tài)變量持有Activity 引用刁俭,生命周期可能不會比Activity 長橄仍,應(yīng)該不一定會導(dǎo)致內(nèi)存泄露呢,顯然不是這樣的牍戚!
熟悉Handler 消息機(jī)制的都知道侮繁,mHandler 會作為成員變量保存在發(fā)送的消息msg 中,即msg 持有mHandler 的引用如孝,而mHandler 是Activity 的非靜態(tài)內(nèi)部類實例宪哩,即mHandler 持有Activity 的引用,那么我們就可以理解為msg 間接持有Activity 的引用第晰。msg 被發(fā)送后先放到消息隊列MessageQueue 中锁孟,然后等待Looper 的輪詢處理(MessageQueue 和Looper 都是與線程相關(guān)聯(lián)的,MessageQueue 是Looper 引用的成員變量茁瘦,而Looper 是保存在ThreadLocal 中的)品抽。那么當(dāng)Activity退出后,msg 可能仍然存在于消息對列MessageQueue 中未處理或者正在處理甜熔,那么這樣就會導(dǎo)致Activity 無法被回收圆恤,以致發(fā)生Activity 的內(nèi)存泄露。
通常在Android 開發(fā)中如果要使用內(nèi)部類腔稀,但又要規(guī)避內(nèi)存泄露盆昙,一般都會采用靜態(tài)內(nèi)部類+弱引用的方式.mHandler 通過弱引用的方式持有Activity,當(dāng)GC 執(zhí)行垃圾回收時焊虏,遇到Activity 就會回收并釋放所占據(jù)的內(nèi)存單元淡喜。這樣就不會發(fā)生內(nèi)存泄露了。
上面的做法確實避免了Activity 導(dǎo)致的內(nèi)存泄露诵闭,發(fā)送的msg 不再已經(jīng)沒有持有Activity 的引用了拆火,但是msg 還是有可能存在消息隊列MessageQueue 中,所以更好的是在Activity 銷毀時就將mHandler 的回調(diào)和發(fā)送的消息給移除掉涂圆。
@Overrideprotected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
非靜態(tài)內(nèi)部類造成內(nèi)存泄露還有一種情況就是直接new Thread 或者AsyncTask。正確的做法應(yīng)該是和Handler一樣使用弱引用
4币叹、未取消注冊或未取消回調(diào)導(dǎo)致內(nèi)存泄漏
- 例如:
我們常使用的BroadcastReceiver润歉、EventBus、觀察者使用時候如Retrofit+RxJava網(wǎng)絡(luò)請求的時候
5颈抚、Timer和TimerTask導(dǎo)致內(nèi)存泄漏
觀察者的回調(diào) Timer 和TimerTask 在Android 中通常會被用來做一些計時或循環(huán)任務(wù)踩衩,比如實現(xiàn)無限輪播的ViewPager
所以我們在activity關(guān)閉時候需要在onDestroy()中結(jié)束掉正在運行的Timer和TimerTask,如下:
private void stop() {
if (mTimer != null) {
mTimer.cancel();
mTimer.purge();
mTimer = null;
}
if (mTimerTask != null) {
mTimerTask.cancel();
mTimerTask = null;
}
}
6嚼鹉、集合中對象未清理造成的內(nèi)存泄漏
如果一個對象放入到ArrayList、HashMap 等集合中驱富,這個集合就會持有該對象
的引用锚赤。當(dāng)我們不再需要這個對象時,也并沒有將它從集合中移除褐鸥,這樣只要集合還在使用(而此對象已經(jīng)無用了)线脚,這個對象就造成了內(nèi)存泄露。并且如果集合被靜態(tài)引用的話叫榕,集合里面那些沒有用的對象更會造成內(nèi)存泄露了浑侥。所以在使用集合時要及時將不用的對象從集合remove,或者clear 集合晰绎,以避免內(nèi)存泄漏寓落。
7、資源未關(guān)閉或未釋放導(dǎo)致內(nèi)存泄漏
在使用IO荞下、File 流或者SQLite伶选、Cursor 等資源時要及時關(guān)閉。這些資源在進(jìn)行讀寫操作時通常都
使用了緩沖尖昏,如果及時不關(guān)閉仰税,這些緩沖對象就會一直被占用而得不到釋放,以致發(fā)生內(nèi)存泄露会宪。
因此我們在不需要使用它們的時候就及時關(guān)閉肖卧,以便緩沖能及時得到釋放,從而避免內(nèi)存泄露掸鹅。
8塞帐、屬性動畫造成內(nèi)存泄漏
動畫同樣是一個耗時任務(wù),比如在Activity 中啟動了屬性動畫(ObjectAnimator)巍沙,但是在銷毀
的時候葵姥,沒有調(diào)用cancle 方法,雖然我們看不到動畫了句携,但是這個動畫依然會不斷地播放下去榔幸,
動畫引用所在的控件,所在的控件引用Activity矮嫉,這就造成Activity 無法正常釋放削咆。因此同樣要
在Activity 銷毀的時候cancel 掉屬性動畫,避免發(fā)生內(nèi)存泄漏蠢笋。所以在關(guān)閉activity時候調(diào)用取消方法
@Override
protected void onDestroy() {
super.onDestroy();
mAnimator.cancel();
}
9拨齐、WebView造成的內(nèi)存泄漏
關(guān)于WebView 的內(nèi)存泄露,因為WebView 在加載網(wǎng)頁后會長期占用內(nèi)存而不能被釋放昨寞,因此我
們在Activity 銷毀后要調(diào)用它的destroy()方法來銷毀它以釋放內(nèi)存瞻惋。 另外在查閱WebView 內(nèi)存泄露相關(guān)資料時看到這種情況:
Webview 下面的Callback 持有Activity 引用厦滤,造成Webview 內(nèi)存無法釋放,即使是調(diào)用了
Webview.destory()等方法都無法解決問題(Android5.1 之后)歼狼。
最終的解決方案是:在銷毀WebView 之前需要先將WebView 從父容器中移除掏导,然后在銷毀WebView。
詳細(xì)分析過程請參考這篇文章:
WebView 內(nèi)存泄漏解決方和WebView 內(nèi)存泄漏解決方
法羽峰。
@Override
protected void onDestroy() {
super.onDestroy();
// 先從父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
總結(jié)
*內(nèi)存泄露在Android 內(nèi)存優(yōu)化是一個比較重要的一個方面趟咆,很多時候程序中發(fā)生了內(nèi)存泄露我們
不一定就能注意到,所有在編碼的過程要養(yǎng)成良好的習(xí)慣限寞∪绦ィ總結(jié)下來只要做到以下這幾點就能避
免大多數(shù)情況的內(nèi)存泄漏:
構(gòu)造單例的時候盡量別用Activity 的引用;
靜態(tài)引用時注意應(yīng)用對象的置空或者少用靜態(tài)引用履植;
使用靜態(tài)內(nèi)部類+軟引用代替非靜態(tài)內(nèi)部類计雌;
及時取消廣播或者觀察者注冊;
耗時任務(wù)玫霎、屬性動畫在Activity 銷毀時記得cancel凿滤;
文件流、Cursor 等資源及時關(guān)閉庶近;
Activity 銷毀時WebView 的移除和銷毀翁脆。另外我們在項目開發(fā)中集成LeakCanary工具*
拓展知識:
內(nèi)存泄漏和內(nèi)存溢出有什么區(qū)別和聯(lián)系?
內(nèi)存溢出: out of memory是指程序在申請內(nèi)存時鼻种,沒有足夠的內(nèi)存空間供其使用反番,出現(xiàn)out of memory;
內(nèi)存泄漏:memory leak :是指程序在申請內(nèi)存后叉钥,無法釋放已申請的內(nèi)存空間罢缸,一次內(nèi)存泄漏似乎不會有大的影響,但內(nèi)存泄漏堆積后的后果就是內(nèi)存溢出投队;
內(nèi)存泄漏的堆積最終會導(dǎo)致內(nèi)存溢出內(nèi)存溢出就是你要的內(nèi)存空間超過了系統(tǒng)實際分配給你的空間枫疆,此時系統(tǒng)相當(dāng)于沒法滿足你的需求,就會報內(nèi)存溢出的錯誤敷鸦。內(nèi)存泄漏是指你向系統(tǒng)申請分配內(nèi)存進(jìn)行使用(new)息楔,可是使用完了以后卻不歸還(delete),結(jié)果你申請到的那塊內(nèi)存你自己也不能再訪問(也許你把它的地址給弄丟了)扒披,而系統(tǒng)也不能再次將它分配給需要的程序值依。就相當(dāng)于你租了個帶鑰匙的柜子,你存完東西之后把柜子鎖上之后碟案,把鑰匙丟了或者沒有將鑰匙還回去鳞滨,那么結(jié)果就是這個柜子將無法供給任何人使用,也無法被垃圾回收器回收蟆淀,因為找不到他的任何信息拯啦。內(nèi)存溢出:一個盤子用盡各種方法只能裝4個果子,你裝了5個熔任,結(jié)果掉倒地上不能吃了褒链。這就是溢出。比方說棧疑苔,棧滿時再做進(jìn)棧必定產(chǎn)生空間溢出甫匹,叫上溢,椀敕眩空時再做退棧也產(chǎn)生空間溢出兵迅,稱為下溢。就是分配的內(nèi)存不足以放下數(shù)據(jù)項序列,稱為內(nèi)存溢出薪贫。說白了就是我承受不了那么多恍箭,那我就報錯。