一、什么是內(nèi)存泄漏?
當一個對象已經(jīng)不需要在使用了,本應該被回收,而另一個正在使用的對象持有它的引用,導致對象不能被回收。因為不能被及時回收的本該被回收的內(nèi)存,就產(chǎn)生了內(nèi)存泄漏。如果內(nèi)存泄漏太多會導致程序沒有辦法申請內(nèi)存,最后出現(xiàn)內(nèi)存溢出的錯誤。
二、android中導致內(nèi)存泄漏的主要幾個點
android開發(fā)中經(jīng)常出現(xiàn)的點,我有只有了解了,才能更好的避免。
使用單例模式
使用匿名內(nèi)部類
使用異步事件處理機制Handler
使用靜態(tài)變量
資源未關(guān)閉
設(shè)置監(jiān)聽
使用AsyncTask
使用Bitmap
上面就是我列出的幾個常出現(xiàn)內(nèi)存泄漏的幾個點,下面我們將一一解讀。
三、java虛擬機內(nèi)存管理
java虛擬機內(nèi)存分為虛擬機棧,本地方法棧,程序計數(shù)器,堆,方法區(qū)這幾個模塊,下面我們就來分析下各個模塊。
(1).虛擬機棧
虛擬機棧主要的作用就是為執(zhí)行java方法服務的,是Java方法執(zhí)行的動態(tài)內(nèi)存模型。會導致棧內(nèi)存溢出(StackOverFlowError)
(2).本地方法棧
為執(zhí)行native方法服務的,其他和虛擬機棧一樣
(3).程序計數(shù)器
是當前線程執(zhí)行的字節(jié)碼行號指示器
處于線程獨占區(qū)
如果是執(zhí)行的是java代碼,當前值為字節(jié)碼指令的地址,如果是Native,值為undefined
(4).堆
存放對象的實例
垃圾收集器管理的主要區(qū)域
分代管理對象
會導致內(nèi)存溢出(OutOfMemoryError)
(5).方法區(qū)
存放虛擬機加載的類信息,常量,靜態(tài)變量,編譯后的代碼和數(shù)據(jù)
GC主要對方法區(qū)進行常量回收和類卸載
會出現(xiàn)內(nèi)存溢出(OutOfMemoryError)
四、java內(nèi)存幾種分配策略?
可以結(jié)合上面的內(nèi)存分配模型,能很好的理解。
(1).靜態(tài)的
靜態(tài)存儲區(qū):內(nèi)存在程序編譯期間就已經(jīng)分配完成,一般來說,這個區(qū)域在程序運行期間一直處在
它主要儲存靜態(tài)數(shù)據(jù),全局靜態(tài)數(shù)據(jù)和常量
(2).棧式的
執(zhí)行方法時,存儲局部變量(編譯期間,已經(jīng)確定占用內(nèi)存大小),操作數(shù),動態(tài)鏈接,方法出口
(3).堆式的
也叫動態(tài)內(nèi)存分配,主要存儲對象實例,以及已經(jīng)被加載類的Class對象(用于反射)
五、垃圾收集器是如何判斷對象是否可回收?
我們知道內(nèi)存泄漏的原因是應該被回收的對象,不能被及時回收,那么GC是如何來判斷對象是否為垃圾對象呢?
判斷的方式有兩個:
引用計數(shù)
對象被引用,引用計數(shù)器加1,反之減一,只有引用計數(shù)為0,那么這個對象為垃圾對象
可達性
從GCRoot節(jié)點對象開始,看是否可以訪問到此對象,如果沒有訪問到則為垃圾對象
可以作為GCRoot對象有以下幾種:
虛擬機棧中的局部變量
本地方法棧中的引用對象
方法區(qū)中的常量引用對象
方法區(qū)中的類屬性引用對象
在native層和早期的虛擬機一般使用引用計數(shù),但是現(xiàn)在的java虛擬機大多使用的是可達性。
六、什么是內(nèi)存抖動?
堆內(nèi)存都有一定的大小,能容納的數(shù)據(jù)是有限制的,當Java堆的大小太大時,垃圾收集會啟動停止堆中不再應用的對象,來釋放內(nèi)存。當在極短時間內(nèi)分配給對象和回收對象的過程就是內(nèi)存抖動。
七、內(nèi)存抖動產(chǎn)生的原因?
從術(shù)語上來講就是極短時間內(nèi)分配給對象和回收對象的過程。
一般多是在循環(huán)語句中創(chuàng)建臨時對象,在繪制時配置大量對象或者執(zhí)行動畫時創(chuàng)建大量臨時對象
內(nèi)存抖動會帶來UI的卡頓,因為大量的對象創(chuàng)建,會很快消耗剩余內(nèi)存,導致GC回收,GC會占用大量的幀繪制時間,從而導致UI卡頓,關(guān)于UI卡頓會在后面章節(jié)講到。
八、android中4種引用
(1).StrongReference強引用
從不被回收,java虛擬機停止時,才終止
(2).SoftReference軟引用
當內(nèi)存不足時,會主動回收,使用SoftReference使用結(jié)合ReferenceQueue構(gòu)造有效期短
(3).WeakReference弱引用
每次垃圾回收時,被回收
(4).PhatomReference虛引用
每次垃圾回收時,被回收.結(jié)合ReferenceQueue來跟蹤對象被垃圾回收器回收的活動
九、常見的導致內(nèi)存泄漏的示例
(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對象,那么就會產(chǎn)生內(nèi)存泄漏。因為當前對象ComonUtil是靜態(tài)的,生命周期和應用是一樣的,只有應用退出才會釋放,導致Activity不能及時釋放,帶來內(nèi)存泄漏。
怎么解決呢?
常見的有兩種方式,第一就是傳入ApplicationContext,第二CommonUtil中取context.getApplicationContext()。
public ComonUtil(Context context) { mContext = context.getApplicationContext(); }
(2).使用非靜態(tài)內(nèi)部類
/** * 非靜態(tài)內(nèi)部類 */ 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調(diào)用了createNonStaticInnerClass方法,然后退出當前Activity時,因為線程還在后臺執(zhí)行且當前線程持有Activity引用,只有等到線程執(zhí)行完畢,Activitiy才能得到釋放,導致內(nèi)存泄漏。
常用的解決方法有很多,第一把線程類聲明為靜態(tài)的類,如果要用到Activity對象,那么就作為參數(shù)傳入且為WeakReference,第二在Activity的onDestroy時,停止線程的執(zhí)行。
public static class CustomThread extends Thread{ private WeakReferencemActivity; public CustomThread(MainActivity activity){ mActivity = new WeakReference (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不能及時的釋放,引起內(nèi)存泄漏的問題。
解決handler引起的內(nèi)存泄漏問題常用的兩種方式:
1.和上面解決Thread的方式一樣,
2.在onDestroy中調(diào)用mHandler.removeCallbacksAndMessages(null)
@Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
(4).使用靜態(tài)變量
同單例引起的內(nèi)存泄漏。
(5).資源未關(guān)閉
常見的就是數(shù)據(jù)庫游標沒有關(guān)閉,對象文件流沒有關(guān)閉,主要記得關(guān)閉就OK了。
(6).設(shè)置監(jiān)聽
常見的是在觀察者模式中出現(xiàn),我們在退出Acviity時沒有取消監(jiān)聽,導致被觀察者還持有當前Activity的引用,從而引起內(nèi)存泄漏。
常見的解決方法就是在onPause中注消監(jiān)聽
(7).使用AsyncTask
public AsyncTask
和上面同樣的道理,匿名內(nèi)部類持有外部類的引用,AsyncTask耗時操作導致Activity不能及時釋放,引起內(nèi)存泄漏。
解決方法同上:
1.聲明為靜態(tài)類,
2.在onPause中取消任務
(8).使用Bitmap
我們知道當bitmap對象沒有被使用(引用),gc會回收bitmap的占用內(nèi)存,當時這邊的內(nèi)存指的是java層的,那么本地內(nèi)存的釋放呢?我們可以通過調(diào)用bitmap.recycle()來釋放C層上的內(nèi)存,防止本地內(nèi)存泄漏
審核編輯:劉清
-
Android
+關(guān)注
關(guān)注
12文章
3937瀏覽量
127516 -
JAVA
+關(guān)注
關(guān)注
19文章
2970瀏覽量
104836 -
虛擬機
+關(guān)注
關(guān)注
1文章
918瀏覽量
28257 -
內(nèi)存泄漏
+關(guān)注
關(guān)注
0文章
39瀏覽量
9226
原文標題:Android內(nèi)存泄漏知識點
文章出處:【微信號:哆啦安全,微信公眾號:哆啦安全】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論