和你一起終身學習,這裏是程序員 Android
經典好文推薦,通過閱讀本文,您將收穫以下知識點:
一、什麼是內存泄漏?
二、android中導致內存泄漏的主要幾個點
三、java虛擬機內存管理
四、java內存幾種分配策略?
五、垃圾收集器是如何判斷對象是否可回收?
六、什麼是內存抖動?
七、內存抖動產生的原因?
八、android中4種引用
九、常見的導致內存泄漏的示例
下面我們就以上面幾個知識點來進行逐一的分析:
一、什麼是內存泄漏?
當一個對象已經不需要在使用了,本應該被回收,而另一個正在使用的對象持有它的引用,導致對象不能被回收。因爲不能被及時回收的本該被回收的內存,就產生了內存泄漏。如果內存泄漏太多會導致程序沒有辦法申請內存,最後出現內存溢出的錯誤。
二、android中導致內存泄漏的主要幾個點
android開發中經常出現的點,我有隻有了解了,才能更好的避免。
- 使用單例模式
- 使用匿名內部類
- 使用異步事件處理機制Handler
- 使用靜態變量
- 資源未關閉
- 設置監聽
- 使用AsyncTask
- 使用Bitmap
上面就是我列出的幾個常出現內存泄漏的幾個點,下面我們將一一解讀。
三、java虛擬機內存管理
java虛擬機內存分爲虛擬機棧,本地方法棧,程序計數器,堆,方法區這幾個模塊,下面我們就來分析下各個模塊。
(1).虛擬機棧
虛擬機棧主要的作用就是爲執行java方法服務的,是Java方法執行的動態內存模型。會導致棧內存溢出(StackOverFlowError)
(2).本地方法棧
爲執行native方法服務的,其他和虛擬機棧一樣
(3).程序計數器
是當前線程執行的字節碼行號指示器
處於線程獨佔區
如果是執行的是java代碼,當前值爲字節碼指令的地址,如果是Native,值爲undefined
(4).堆
存放對象的實例
垃圾收集器管理的主要區域
分代管理對象
會導致內存溢出(OutOfMemoryError)
(5).方法區
存放虛擬機加載的類信息,常量,靜態變量,編譯後的代碼和數據
GC主要對方法區進行常量回收和類卸載
會出現內存溢出(OutOfMemoryError)
四、java內存幾種分配策略?
可以結合上面的內存分配模型,能很好的理解。
(1).靜態的
靜態存儲區:內存在程序編譯期間就已經分配完成,一般來說,這個區域在程序運行期間一直處在
它主要儲存靜態數據,全局靜態數據和常量
(2).棧式的
執行方法時,存儲局部變量(編譯期間,已經確定佔用內存大小),操作數,動態鏈接,方法出口
(3).堆式的
也叫動態內存分配,主要存儲對象實例,以及已經被加載類的Class對象(用於反射)
五、垃圾收集器是如何判斷對象是否可回收?
我們知道內存泄漏的原因是應該被回收的對象,不能被及時回收,那麼GC是如何來判斷對象是否爲垃圾對象呢?
判斷的方式有兩個:
引用計數
對象被引用,引用計數器加1,反之減一,只有引用計數爲0,那麼這個對象爲垃圾對象可達性
從GCRoot節點對象開始,看是否可以訪問到此對象,如果沒有訪問到則爲垃圾對象
可以作爲GCRoot對象有以下幾種:
虛擬機棧中的局部變量
本地方法棧中的引用對象
方法區中的常量引用對象
方法區中的類屬性引用對象
在native層和早期的虛擬機一般使用引用計數,但是現在的java虛擬機大多使用的是可達性。
六、什麼是內存抖動?
堆內存都有一定的大小,能容納的數據是有限制的,當Java堆的大小太大時,垃圾收集會啓動停止堆中不再應用的對象,來釋放內存。當在極短時間內分配給對象和回收對象的過程就是內存抖動。
七、內存抖動產生的原因?
從術語上來講就是極短時間內分配給對象和回收對象的過程。
一般多是在循環語句中創建臨時對象,在繪製時配置大量對象或者執行動畫時創建大量臨時對象
內存抖動會帶來UI的卡頓,因爲大量的對象創建,會很快消耗剩餘內存,導致GC回收,GC會佔用大量的幀繪製時間,從而導致UI卡頓,關於UI卡頓會在後面章節講到。
八、android中4種引用
(1).StrongReference強引用
從不被回收,java虛擬機停止時,才終止
(2).SoftReference軟引用
當內存不足時,會主動回收,使用SoftReference使用結合ReferenceQueue構造有效期短
(3).WeakReference弱引用
每次垃圾回收時,被回收
(4).PhatomReference虛引用
每次垃圾回收時,被回收.結合ReferenceQueue來跟蹤對象被垃圾回收器回收的活動
九、常見的導致內存泄漏的示例
(1).使用單例模式
private static ComonUtil mInstance = null;
private Context mContext = null;
public ComonUtil(Context context) {
mContext = context;
}
public static ComonUtil getInstance(Context context) {
if (mInstance == null) {
mInstance = new ComonUtil(context);
}
return mInstance;
}
使用:
ComonUtil mComonUtil = ComonUtil.getInstance(this);
我們看到上面的代碼就是我們平時使用的單例模式,當然這裏沒有考慮線程安全,請忽略。當我們傳遞進來的是Context,那麼當前對象就會持有第一次實例化的Context,如果Context是Activity對象,那麼就會產生內存泄漏。因爲當前對象ComonUtil是靜態的,生命週期和應用是一樣的,只有應用退出纔會釋放,導致Activity不能及時釋放,帶來內存泄漏。
怎麼解決呢?
常見的有兩種方式,第一就是傳入ApplicationContext,第二CommonUtil中取context.getApplicationContext()。
public ComonUtil(Context context) {
mContext = context.getApplicationContext();
}
(2).使用非靜態內部類
/**
* 非靜態內部類
*/
public void createNonStaticInnerClass(){
CustomThread mCustomThread = new CustomThread();
mCustomThread.start();
}
public class CustomThread extends Thread{
@Override
public void run() {
super.run();
while (true){
try {
Thread.sleep(5000);
Log.i(TAG,"CustomThread ------- 打印");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我們就以線程爲例,當Activity調用了createNonStaticInnerClass方法,然後退出當前Activity時,因爲線程還在後臺執行且當前線程持有Activity引用,只有等到線程執行完畢,Activitiy才能得到釋放,導致內存泄漏。
常用的解決方法有很多,第一把線程類聲明爲靜態的類,如果要用到Activity對象,那麼就作爲參數傳入且爲WeakReference,第二在Activity的onDestroy時,停止線程的執行。
public static class CustomThread extends Thread{
private WeakReference<MainActivity> mActivity;
public CustomThread(MainActivity activity){
mActivity = new WeakReference<MainActivity>(activity)
}
}
(3).使用異步事件處理機制Handler
/**
* 異步消息處理機制 -- handler機制
*/
public void createHandler(){
mHandler.sendEmptyMessage(0);
}
public Handler mHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
//處理耗時操作
return false;
}
});
這個應該是我們平時使用最多的一種方式,如果當handler中處理的是耗時操作,或者當前消息隊列中消息很多時,那當Activity退出時,當前message中持有handler的引用,handler又持有Activity的引用,導致Activity不能及時的釋放,引起內存泄漏的問題。
解決handler引起的內存泄漏問題常用的兩種方式:
1.和上面解決Thread的方式一樣,
2.在onDestroy中調用mHandler.removeCallbacksAndMessages(null)
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
(4).使用靜態變量
同單例引起的內存泄漏。
(5).資源未關閉
常見的就是數據庫遊標沒有關閉,對象文件流沒有關閉,主要記得關閉就OK了。
(6).設置監聽
常見的是在觀察者模式中出現,我們在退出Acviity時沒有取消監聽,導致被觀察者還持有當前Activity的引用,從而引起內存泄漏。
常見的解決方法就是在onPause中注消監聽
(7).使用AsyncTask
public AsyncTask<Object, Object, Object> mTask = new AsyncTask<Object, Object, Object>() {
@Override
protected Object doInBackground(Object... params) {
//耗時操作
return null;
}
@Override
protected void onPostExecute(Object o) {
}
};
和上面同樣的道理,匿名內部類持有外部類的引用,AsyncTask耗時操作導致Activity不能及時釋放,引起內存泄漏。
解決方法同上:
1.聲明爲靜態類,
2.在onPause中取消任務
(8).使用Bitmap
我們知道當bitmap對象沒有被使用(引用),gc會回收bitmap的佔用內存,當時這邊的內存指的是java層的,那麼本地內存的釋放呢?我們可以通過調用bitmap.recycle()來釋放C層上的內存,防止本地內存泄漏
原文鏈接:https://www.jianshu.com/p/797395731747
至此,本篇已結束。轉載網絡的文章,小編覺得很優秀,歡迎點擊閱讀原文,支持原創作者,如有侵權,懇請聯繫小編刪除,歡迎您的建議與指正。同時期待您的關注,感謝您的閱讀,謝謝!