Android Studio+MAT實(shí)戰(zhàn)內(nèi)存泄漏
對(duì)于內(nèi)存泄漏,在Android中如果不注意的話(huà),還是很容易出現(xiàn)的,尤其是在Activity中,比較容易出現(xiàn),下面我就說(shuō)下自己是如何查找內(nèi)存泄露的。
首先什么是內(nèi)存泄漏?
內(nèi)存泄漏就是一些已經(jīng)不使用的對(duì)象還存在于內(nèi)存之中且垃圾回收機(jī)制無(wú)法回收它們,導(dǎo)致它們常駐內(nèi)存,會(huì)使內(nèi)存消耗越來(lái)越大,最終導(dǎo)致程序性能變差。
其中在Android虛擬機(jī)中采用的是根節(jié)點(diǎn)搜索算法枚舉根節(jié)點(diǎn)判斷是否是垃圾,虛擬機(jī)會(huì)從GC Roots開(kāi)始遍歷,如果一個(gè)節(jié)點(diǎn)找不到一條到達(dá)GC Roots的路線(xiàn),也就是沒(méi)和GC Roots 相連,那么就證明該引用無(wú)效,可以被回收,內(nèi)存泄漏就是存在一些不好的調(diào)用導(dǎo)致一些無(wú)用對(duì)象和GC Roots相連,無(wú)法被回收。
既然知道了什么是內(nèi)存泄漏,自然就知道如何去避免了,就是我們?cè)趯?xiě)代碼的時(shí)候盡量注意產(chǎn)生對(duì)無(wú)用對(duì)象長(zhǎng)時(shí)間的引用,說(shuō)起來(lái)簡(jiǎn)單,但是需要足夠的經(jīng)驗(yàn)才能達(dá)到,所以?xún)?nèi)存泄漏還是比較容易出現(xiàn)的,既然不容易完全避免,那么我們就要能發(fā)現(xiàn)程序中出現(xiàn)的內(nèi)存泄漏并修復(fù)它,
下面我就說(shuō)說(shuō)如何發(fā)現(xiàn)內(nèi)存泄漏的吧。
查找內(nèi)存泄漏:
比如說(shuō)下面這個(gè)代碼:
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String string = new String(); } public void click(View view){ Intent intent = new Intent(); intent.setClass(getApplicationContext(),SecondActivity.class); startActivity(intent); } }
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } }
每次跳轉(zhuǎn)到這個(gè)Activity中時(shí)都會(huì)調(diào)用一個(gè)線(xiàn)程,然后這個(gè)線(xiàn)程會(huì)執(zhí)行runnable的run方法 由于Runnable是一個(gè)匿名內(nèi)部對(duì)象 所以握有SecondActivity的引用,因此很簡(jiǎn)單的兩個(gè)Activity,可由MainActivity跳轉(zhuǎn)到SecondActivity中,下面我們從MainActivity跳到SecondActivity 然后從SecondActivity返回MainActivity,連續(xù)這樣5次 ,最終返回MainActivity,按照常理來(lái)說(shuō),我們從SecondActivity返回MainActivity之后 SecondActivity就該被銷(xiāo)毀回收,可實(shí)際可能并不是這樣。
這時(shí)候要判斷發(fā)沒(méi)發(fā)生內(nèi)存溢出就要使用工具了!下面有兩種方式
1.利用MAT工具查找
首先打開(kāi)AS中的Android Device Monitor工具 具體位置如下圖:
打開(kāi)后會(huì)出現(xiàn)如下的界面
先選中你要檢測(cè)的應(yīng)用的包名,然后點(diǎn)擊下圖畫(huà)圈的地方,會(huì)在程序包名后標(biāo)記一個(gè)圖標(biāo)
接下來(lái)要做的就是操作我們的app 來(lái)回跳轉(zhuǎn)5次。
之后點(diǎn)擊下圖的圖標(biāo) 就可導(dǎo)出hprof文件進(jìn)行分析了
導(dǎo)出文件如下圖所示:
得到了hprof文件 我們就可以利用MAT工具進(jìn)行分析了,
打開(kāi)MAT工具
如果沒(méi)有 可以在下面網(wǎng)址下載
MAT工具下載地址
界面如下圖所示:
打開(kāi)我們先前導(dǎo)出的hprof文件 ,不出意外會(huì)報(bào)下面的錯(cuò)誤
這是因?yàn)镸AT是用來(lái)分析java程序的hprof文件的 與Android導(dǎo)出的hprof有一定的格式區(qū)別,因此我們需要把導(dǎo)出的hprof文件轉(zhuǎn)換一下,sdk中提供給我們轉(zhuǎn)換的工具 hprof-conv.exe 在下圖的位置
接下來(lái)我們cd到這個(gè)路徑下執(zhí)行這個(gè)命令轉(zhuǎn)換我們的hprof文件即可,如下圖
其中 hprof-conv 命令 這樣使用
hprof-conv 源文件 輸出文件
比如 hprof-conv E:\aaa.hprof E:\output.hprof
就是 把a(bǔ)aa.hprof 轉(zhuǎn)換為output.hprof輸出 output.hprof就是我們轉(zhuǎn)換之后的文件,圖中 mat2.hprof就是我們轉(zhuǎn)換完的文件。
接下來(lái) 我們用MAT工具打開(kāi)轉(zhuǎn)換之后的mat2.hprof文件即可 ,打開(kāi)后不報(bào)錯(cuò) 如下圖所示:
之后我們就可以查看當(dāng)前內(nèi)存中存在的對(duì)象了,由于我們內(nèi)存泄漏一般發(fā)生在Activity中,因此只需要查找Activity即可。
點(diǎn)擊下圖中標(biāo)記的QQL圖標(biāo) 輸入 select * from instanceof android.app.Activity
類(lèi)似于 SQL語(yǔ)句 查找 Activity相關(guān)的信息 點(diǎn)擊 紅色嘆號(hào)執(zhí)行后 如下圖所示:
接下來(lái) 我們就可以看到下面過(guò)濾到的Activity信息了
如上圖所示, 其中內(nèi)存中還存在 6個(gè)SecondActivity實(shí)例,但是我們是想要全部退出的,這表明出現(xiàn)了內(nèi)存泄漏
其中 有 Shallow size 和 Retained Size兩個(gè)屬性
Shallow Size 對(duì)象自身占用的內(nèi)存大小,不包括它引用的對(duì)象。針對(duì)非數(shù)組類(lèi)型的對(duì)象,它的大小就是對(duì)象與它所有的成員變量大小的總和。 當(dāng)然這里面還會(huì)包括一些java語(yǔ)言特性的數(shù)據(jù)存儲(chǔ)單元。針對(duì)數(shù)組類(lèi)型的對(duì)象,它的大小是數(shù)組元素對(duì)象的大小總和。 Retained Size Retained Size=當(dāng)前對(duì)象大小+當(dāng)前對(duì)象可直接或間接引用到的對(duì)象的大小總和。(間接引用的含義:A->B->C, C就是間接引用) 不過(guò),釋放的時(shí)候還要排除被GC Roots直接或間接引用的對(duì)象。他們暫時(shí)不會(huì)被被當(dāng)做Garbage。
接下來(lái) 右擊一個(gè)SecondActivity
選擇 with all references
打開(kāi)如下圖所示的頁(yè)面
查看下圖的頁(yè)面
看到 this0引用了這個(gè)Activity而this0是表示 內(nèi)部類(lèi)的意思,也就是一個(gè)內(nèi)部類(lèi)引用了Activity 而 this$0又被 target引用 target是一個(gè)線(xiàn)程,原因找到了,內(nèi)存泄漏的原因 就是 Activity被 內(nèi)部類(lèi)引用 而內(nèi)部類(lèi)又被線(xiàn)程使用 因此無(wú)法釋放,我們轉(zhuǎn)到這個(gè)類(lèi)的代碼處查看
public class SecondActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_second); Runnable runnable = new Runnable() { @Override public void run() { try { Thread.sleep(8000000L); } catch (InterruptedException e) { e.printStackTrace(); } } }; new Thread(runnable).start(); } } 確實(shí) 在
確實(shí) 在 SecondActivity中 存在Runnable 內(nèi)部類(lèi)對(duì)象,然后又被線(xiàn)程 使用,而線(xiàn)程要執(zhí)行8000秒 因此 SecondActivity對(duì)象被引用 無(wú)法釋放,導(dǎo)致了內(nèi)存溢出。
要解決這種的內(nèi)存溢出,要及時(shí)在Activity退出時(shí)結(jié)束線(xiàn)程(不過(guò)不大好結(jié)束。。),或者良好的控制線(xiàn)程執(zhí)行的時(shí)間即可。
這樣我們就找出了這個(gè)程序中的內(nèi)存溢出。
2.直接利用Android Studio的 Monitor Memory 查找內(nèi)存溢出
還是利用上面那個(gè)程序,我就簡(jiǎn)單點(diǎn)說(shuō)了。
首先 在手機(jī)上運(yùn)行程序,打開(kāi)AS的 Minotor 界面 查看Memory 圖像
點(diǎn)擊 小卡車(chē)圖標(biāo)(圖中1位置圖標(biāo)) 可以觸發(fā)一次 GC
點(diǎn)擊 圖中2位置圖標(biāo)可以查看hprof文件
左邊是 內(nèi)存中的對(duì)象,在里面找 Activity 看存不存在我們希望已經(jīng)回收的Activity 如果 出現(xiàn)我們期望已經(jīng)回收的Activity,單擊 就會(huì)在右邊顯示它的總的個(gè)數(shù),點(diǎn)擊右邊的某個(gè),可以顯示 它的GC Roots的樹(shù)關(guān)系圖 ,查看關(guān)系圖就可以找出發(fā)生內(nèi)存泄漏的位置(類(lèi)似于第一種方式)
這樣就完成了內(nèi)存泄漏的查找。
其中內(nèi)存泄漏產(chǎn)生的原因在Android中大致分為以下幾種:
1.static變量引起的內(nèi)存泄漏
因?yàn)閟tatic變量的生命周期是在類(lèi)加載時(shí)開(kāi)始 類(lèi)卸載時(shí)結(jié)束,也就是說(shuō)static變量是在程序進(jìn)程死亡時(shí)才釋放,如果在static變量中 引用了Activity 那么 這個(gè)Activity由于被引用,便會(huì)隨static變量的生命周期一樣,一直無(wú)法被釋放,造成內(nèi)存泄漏。
解決辦法:
在Activity被靜態(tài)變量引用時(shí),使用 getApplicationContext 因?yàn)锳pplication生命周期從程序開(kāi)始到結(jié)束,和static變量的一樣。
2.線(xiàn)程造成的內(nèi)存泄漏
類(lèi)似于上述例子中的情況,線(xiàn)程執(zhí)行時(shí)間很長(zhǎng),及時(shí)Activity跳出還會(huì)執(zhí)行,因?yàn)榫€(xiàn)程或者Runnable是Acticvity內(nèi)部類(lèi),因此握有Activity的實(shí)例(因?yàn)閯?chuàng)建內(nèi)部類(lèi)必須依靠外部類(lèi)),因此造成Activity無(wú)法釋放。
AsyncTask 有線(xiàn)程池,問(wèn)題更嚴(yán)重
解決辦法:
1.合理安排線(xiàn)程執(zhí)行的時(shí)間,控制線(xiàn)程在Activity結(jié)束前結(jié)束。
2.將內(nèi)部類(lèi)改為靜態(tài)內(nèi)部類(lèi),并使用弱引用WeakReference來(lái)保存Activity實(shí)例 因?yàn)槿跻?只要GC發(fā)現(xiàn)了 就會(huì)回收它 ,因此可盡快回收
3.BitMap占用過(guò)多內(nèi)存
bitmap的解析需要占用內(nèi)存,但是內(nèi)存只提供8M的空間給BitMap,如果圖片過(guò)多,并且沒(méi)有及時(shí) recycle bitmap 那么就會(huì)造成內(nèi)存溢出。
解決辦法:
及時(shí)recycle 壓縮圖片之后加載圖片
4.資源未被及時(shí)關(guān)閉造成的內(nèi)存泄漏
比如一些Cursor 沒(méi)有及時(shí)close 會(huì)保存有Activity的引用,導(dǎo)致內(nèi)存泄漏
解決辦法:
在onDestory方法中及時(shí) close即可
5.Handler的使用造成的內(nèi)存泄漏
由于在Handler的使用中,handler會(huì)發(fā)送message對(duì)象到 MessageQueue中 然后 Looper會(huì)輪詢(xún)MessageQueue 然后取出Message執(zhí)行,但是如果一個(gè)Message長(zhǎng)時(shí)間沒(méi)被取出執(zhí)行,那么由于 Message中有 Handler的引用,而 Handler 一般來(lái)說(shuō)也是內(nèi)部類(lèi)對(duì)象,Message引用 Handler ,Handler引用 Activity 這樣 使得 Activity無(wú)法回收。
解決辦法:
依舊使用 靜態(tài)內(nèi)部類(lèi)+弱引用的方式 可解決
其中還有一些關(guān)于 集合對(duì)象沒(méi)移除,注冊(cè)的對(duì)象沒(méi)反注冊(cè),代碼壓力的問(wèn)題也可能產(chǎn)生內(nèi)存泄漏,但是使用上述的幾種解決辦法一般都是可以解決的。
相關(guān)文章
簡(jiǎn)單實(shí)現(xiàn)Android數(shù)獨(dú)游戲
這篇文章主要教大家如何簡(jiǎn)單實(shí)現(xiàn)Android數(shù)獨(dú)游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-12-12Android通過(guò)Path實(shí)現(xiàn)搜索按鈕和時(shí)鐘復(fù)雜效果
這篇文章主要為大家詳細(xì)介紹了Android通過(guò)Path實(shí)現(xiàn)搜索按鈕和時(shí)鐘復(fù)雜效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09Android中使用Expandablelistview實(shí)現(xiàn)微信通訊錄界面
本文主要介紹了Android中使用Expandablelistview實(shí)現(xiàn)微信通訊錄界面(完善防微信APP)的方法,具有一定的參考價(jià)值,下面跟著小編一起來(lái)看下吧2016-12-12android 9.0 launcher3 去掉抽屜式顯示所有 app(代碼詳解)
本文通過(guò)實(shí)例代碼給大家介紹了android 9.0 Launcher3 去掉抽屜式,顯示所有 app,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-11-11Android圖片處理:識(shí)別圖像方向并顯示實(shí)例教程
在Android中使用ImageView顯示圖片的時(shí)候發(fā)現(xiàn)圖片顯示不正,方向偏了或者倒過(guò)來(lái)了,下面與大家分享下具體的解決方法,感性的朋友可以參考下2013-06-06OpenGL Shader實(shí)例分析(1)Wave效果
這篇文章主要為大家詳細(xì)介紹了OpenGL Shader實(shí)例分析第一篇,Wave效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02Android RecyclerView滑動(dòng)刪除和拖動(dòng)排序
這篇文章主要介紹了Android RecyclerView滑動(dòng)刪除和拖動(dòng)排序的相關(guān)資料,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-07-07Android 高仿斗魚(yú)滑動(dòng)驗(yàn)證碼
這篇文章主要介紹了Android 高仿斗魚(yú)滑動(dòng)驗(yàn)證碼的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-11-11Android ViewDragHelper實(shí)現(xiàn)京東、淘寶拖拽詳情功能的實(shí)現(xiàn)
這篇文章主要介紹了Android ViewDragHelper實(shí)現(xiàn)京東、淘寶拖拽詳情,實(shí)現(xiàn)這種效果大概分為三種方式,具體哪三種方式大家通過(guò)本文了解下吧2018-04-04在Android環(huán)境下WebView中攔截所有請(qǐng)求并替換URL示例詳解
這篇文章主要介紹了在Android環(huán)境下WebView中攔截所有請(qǐng)求并替換URL示例詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07