Android Studio和 MAT 內(nèi)存泄漏分析

和你一起終身學(xué)習(xí),這里是程序員Android
經(jīng)典好文推薦,通過(guò)閱讀本文,您將收獲以下知識(shí)點(diǎn):
一、Java內(nèi)存分配策略
二、堆與棧的區(qū)別
三、Java管理內(nèi)存的機(jī)制
四、Java中的內(nèi)存泄漏
五、Android中常見的內(nèi)存泄漏
六、Android中內(nèi)存泄漏的排查與分析
七、總結(jié)
一、Java內(nèi)存分配策略
Java 程序運(yùn)行時(shí)的內(nèi)存分配策略有三種:靜態(tài)分配、棧式分配和堆式分配。對(duì)應(yīng)的存儲(chǔ)區(qū)域如下:
靜態(tài)存儲(chǔ)區(qū)(方法區(qū)):主要存放靜態(tài)數(shù)據(jù)、全局 static 數(shù)據(jù)和常量。這塊內(nèi)存在程序編譯時(shí)就已經(jīng)分配好,并且在程序整個(gè)運(yùn)行期間都存在。
棧區(qū) :方法體內(nèi)的局部變量都在棧上創(chuàng)建,并在方法執(zhí)行結(jié)束時(shí)這些局部變量所持有的內(nèi)存將會(huì)自動(dòng)被釋放。
堆區(qū) :又稱動(dòng)態(tài)內(nèi)存分配,通常就是指在程序運(yùn)行時(shí)直接 new 出來(lái)的內(nèi)存。這部分內(nèi)存在不使用時(shí)將會(huì)由 Java 垃圾回收器來(lái)負(fù)責(zé)回收。
二、堆與棧的區(qū)別
棧內(nèi)存:在方法體內(nèi)定義的局部變量(一些基本類型的變量和對(duì)象的引用變量)都是在方法的棧內(nèi)存中分配的。當(dāng)在一段方法塊中定義一個(gè)變量時(shí),Java 就會(huì)在棧中為該變量分配內(nèi)存空間,當(dāng)超過(guò)該變量的作用域后,分配給它的內(nèi)存空間也將被釋放掉,該內(nèi)存空間可以被重新使用。
堆內(nèi)存:用來(lái)存放所有由 new 創(chuàng)建的對(duì)象(包括該對(duì)象其中的所有成員變量)和數(shù)組。在堆中分配的內(nèi)存,將由 Java 垃圾回收器來(lái)自動(dòng)管理。在堆中產(chǎn)生了一個(gè)數(shù)組或者對(duì)象后,還可以在棧中定義一個(gè)特殊的變量,這個(gè)變量的取值等于數(shù)組或者對(duì)象在堆內(nèi)存中的首地址,這個(gè)特殊的變量就是我們上面說(shuō)的引用變量。我們可以通過(guò)這個(gè)引用變量來(lái)訪問(wèn)堆中的對(duì)象或者數(shù)組。
例子:
public class A {
int a = 0;
B b = new B();
public void test(){
int a1 = 1;
B b1 = new B();
}
}
A object = new A();
A類內(nèi)的局部變量都存在于棧中,包括基本數(shù)據(jù)類型a1和引用變量b1,b1指向的B對(duì)象實(shí)體存在于堆中
引用變量object存在于棧中,而object指向的對(duì)象實(shí)體存在于堆中,包括這個(gè)對(duì)象的所有成員變量a和b,而引用變量b指向的B類對(duì)象實(shí)體存在于堆中
三、Java管理內(nèi)存的機(jī)制
Java的內(nèi)存管理就是對(duì)象的分配和釋放問(wèn)題。內(nèi)存的分配是由程序員來(lái)完成,內(nèi)存的釋放由GC(垃圾回收機(jī)制)完成。GC 為了能夠正確釋放對(duì)象,必須監(jiān)控每一個(gè)對(duì)象的運(yùn)行狀態(tài),包括對(duì)象的申請(qǐng)、引用、被引用、賦值等。這是Java程序運(yùn)行較慢的原因之一。
釋放對(duì)象的原則:該對(duì)象不再被引用。
GC的工作原理:
將對(duì)象考慮為有向圖的頂點(diǎn),將引用關(guān)系考慮為有向圖的有向邊,有向邊從引用者指向被引對(duì)象。另外,每個(gè)線程對(duì)象可以作為一個(gè)圖的起始頂點(diǎn),例如大多程序從 main 進(jìn)程開始執(zhí)行,那么該圖就是以 main 進(jìn)程為頂點(diǎn)開始的一棵根樹。在有向圖中,根頂點(diǎn)可達(dá)的對(duì)象都是有效對(duì)象,GC將不回收這些對(duì)象。如果某個(gè)對(duì)象與這個(gè)根頂點(diǎn)不可達(dá),那么我們認(rèn)為這個(gè)對(duì)象不再被引用,可以被 GC 回收。
下面舉一個(gè)例子說(shuō)明如何用有向圖表示內(nèi)存管理。對(duì)于程序的每一個(gè)時(shí)刻,我們都有一個(gè)有向圖表示JVM的內(nèi)存分配情況。以下右圖,就是左邊程序運(yùn)行到第6行的示意圖。

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
另外,Java使用有向圖的方式進(jìn)行內(nèi)存管理,可以消除引用循環(huán)的問(wèn)題,例如有三個(gè)對(duì)象相互引用,但只要它們和根進(jìn)程不可達(dá),那么GC也是可以回收它們的。當(dāng)然,除了有向圖的方式,還有一些別的內(nèi)存管理技術(shù),不同的內(nèi)存管理技術(shù)各有優(yōu)缺點(diǎn),在這里就不詳細(xì)展開了。
四、Java中的內(nèi)存泄漏
如果一個(gè)對(duì)象滿足以下兩個(gè)條件:
(1)這些對(duì)象是可達(dá)的,即在有向圖中,存在通路可以與其相連
(2)這些對(duì)象是無(wú)用的,即程序以后不會(huì)再使用這些對(duì)象
就可以判定為Java中的內(nèi)存泄漏,這些對(duì)象不會(huì)被GC所回收,繼續(xù)占用著內(nèi)存。
在C++中,內(nèi)存泄漏的范圍更大一些。有些對(duì)象被分配了內(nèi)存空間,然后卻不可達(dá),由于C++中沒有GC,這些內(nèi)存將永遠(yuǎn)收不回來(lái)。在Java中,這些不可達(dá)的對(duì)象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄漏。

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
五、Android中常見的內(nèi)存泄漏
(1)單例造成的內(nèi)存泄漏

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
這是一個(gè)普通的單例模式,當(dāng)創(chuàng)建這個(gè)單例的時(shí)候,由于需要傳入一個(gè)Context,所以這個(gè)Context的生命周期的長(zhǎng)短至關(guān)重要:
1.如果此時(shí)傳入的是 Application 的 Context,因?yàn)?Application 的生命周期就是整個(gè)應(yīng)用的生命周期,所以沒有任何問(wèn)題。
2.如果此時(shí)傳入的是 Activity 的 Context,當(dāng)這個(gè) Context 所對(duì)應(yīng)的 Activity 退出時(shí),由于該 Context 的引用被單例對(duì)象所持有,其生命周期等于整個(gè)應(yīng)用程序的生命周期,所以當(dāng)前 Activity 退出時(shí)它的內(nèi)存并不會(huì)被回收,這就造成泄漏了。
當(dāng)然,Application 的 context 不是萬(wàn)能的,所以也不能隨便亂用,例如Dialog必須使用 Activity 的 Context。對(duì)于這部分有興趣的讀者可以自行搜索相關(guān)資料。
(2)非靜態(tài)內(nèi)部類創(chuàng)建靜態(tài)實(shí)例造成的內(nèi)存泄漏
public class MainActivity extends AppCompatActivity {
private static TestResource mResource = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(mManager == null){
mManager = new TestResource();
}//...
}
class TestResource {//...
}
}
非靜態(tài)內(nèi)部類默認(rèn)會(huì)持有外部類的引用,而該非靜態(tài)內(nèi)部類又創(chuàng)建了一個(gè)靜態(tài)的實(shí)例,該實(shí)例的生命周期和應(yīng)用的一樣長(zhǎng),這就導(dǎo)致了該靜態(tài)實(shí)例一直會(huì)持有該Activity的引用,導(dǎo)致Activity的內(nèi)存資源不能正常回收。
(3)匿名內(nèi)部類造成的內(nèi)存泄漏
匿名內(nèi)部類默認(rèn)也會(huì)持有外部類的引用。如果在Activity/Fragment中使用了匿名類,并被異步線程持有,如果沒有任何措施這樣一定會(huì)導(dǎo)致泄漏。

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
ref1和ref2的區(qū)別是,ref2使用了匿名內(nèi)部類。我們來(lái)看看運(yùn)行時(shí)這兩個(gè)引用的內(nèi)存:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
可以看到,ref1沒什么特別的。但ref2這個(gè)匿名類的實(shí)現(xiàn)對(duì)象里面多了一個(gè)引用:
this$0這個(gè)引用指向MainActivity.this,也就是說(shuō)當(dāng)前的MainActivity實(shí)例會(huì)被ref2持有,如果將這個(gè)引用再傳入一個(gè)異步線程,此線程和此Acitivity生命周期不一致的時(shí)候,就會(huì)造成Activity的泄漏。
例子:Handler造成的內(nèi)存泄漏

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
在該 MainActivity 中聲明了一個(gè)延遲10分鐘執(zhí)行的消息 Message,mHandler 將其 push 進(jìn)了消息隊(duì)列 MessageQueue 里。當(dāng)該 Activity 被 finish() 掉時(shí),延遲執(zhí)行任務(wù)的 Message 還會(huì)繼續(xù)存在于主線程中,它持有該 Activity 的 Handler 引用,然后又因 為 Handler 為匿名內(nèi)部類,它會(huì)持有外部類的引用(在這里就是指 MainActivity),所以此時(shí) finish() 掉的 Activity 就不會(huì)被回收了,從而造成內(nèi)存泄漏。
修復(fù)方法:在 Activity 中避免使用非靜態(tài)內(nèi)部類或匿名內(nèi)部類,比如將 Handler 聲明為靜態(tài)的,則其存活期跟 Activity 的生命周期就無(wú)關(guān)了。如果需要用到Activity,就通過(guò)弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進(jìn)去。另外, Looper 線程的消息隊(duì)列中還是可能會(huì)有待處理的消息,所以我們?cè)?Activity 的 Destroy 時(shí)或者 Stop 時(shí)應(yīng)該移除消息隊(duì)列 MessageQueue 中的消息。見下面代碼:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
(4)資源未關(guān)閉造成的內(nèi)存泄漏
對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile, Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷毀時(shí)及時(shí)關(guān)閉或者注銷,否則這些資源將不會(huì)被回收,造成內(nèi)存泄漏。
(5)一些不良代碼造成的內(nèi)存壓力
有些代碼并不造成內(nèi)存泄漏,但是它們,或是對(duì)沒使用的內(nèi)存沒進(jìn)行有效及時(shí)的釋放,或是沒有有效的利用已有的對(duì)象而是頻繁的申請(qǐng)新內(nèi)存。比如,Adapter里沒有復(fù)用convertView等。
六、Android中內(nèi)存泄漏的排查與分析
(1)利用Android Studio的Memory Monitor來(lái)檢測(cè)內(nèi)存情況
先來(lái)看一下Android Studio 的 Memory Monitor界面:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
最原始的內(nèi)存泄漏排查方式如下:
重復(fù)多次操作關(guān)鍵的可疑的路徑,從內(nèi)存監(jiān)控工具中觀察內(nèi)存曲線,看是否存在不斷上升的趨勢(shì),且退出一個(gè)界面后,程序內(nèi)存遲遲不降低的話,可能就發(fā)生了嚴(yán)重的內(nèi)存泄漏。
這種方式可以發(fā)現(xiàn)最基本,也是最明顯的內(nèi)存泄漏問(wèn)題,對(duì)用戶價(jià)值最大,操作難度小,性價(jià)比極高。
下面就開始用一個(gè)簡(jiǎn)單的例子來(lái)說(shuō)明一下如何排查內(nèi)存泄漏。
首先,創(chuàng)建了一個(gè)TestActivity類,里面的測(cè)試代碼如下:
@Override
protected void processBiz() {
mHandler = new Handler();
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
MLog.d("------postDelayed------");
}
}, 800000L);
}
運(yùn)行項(xiàng)目,并執(zhí)行以下操作:進(jìn)入TestActivity,然后退出,再重新進(jìn)入,如此操作幾次后,最后最終退出TestActivity。這時(shí)發(fā)現(xiàn),內(nèi)存持續(xù)增高,如圖所示:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
好了,這時(shí)我們可以假設(shè),這里可能出現(xiàn)了內(nèi)存泄漏的情況。那么,如何繼續(xù)定位到內(nèi)存泄漏的地址呢?這時(shí)候就得點(diǎn)擊“Dump java heap”按鈕來(lái)收集具體的信息了。
(2)使用Android Studio生成Java Heap文件來(lái)分析內(nèi)存情況
注意,在點(diǎn)擊 Dump java heap 按鈕之前,一定要先點(diǎn)擊Initate GC按鈕強(qiáng)制GC,建議點(diǎn)擊后等待幾秒后再次點(diǎn)擊,嘗試多次,讓GC更加充分。然后再點(diǎn)擊Dump Java Heap按鈕。
這時(shí)候會(huì)生成一個(gè)Java heap文件并在新的窗口打開:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
這時(shí)候,點(diǎn)擊右上角的“Analyzer Task”,再點(diǎn)擊出現(xiàn)的綠色按鈕,讓Android studio幫我們自動(dòng)分析出有可能潛在的內(nèi)存泄漏的地方:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
如上圖所示,Android studio提示有3個(gè)TestActivity對(duì)象可能出現(xiàn)了內(nèi)存泄漏。而且左邊的Reference Tree(引用樹),也大概列出了該實(shí)體類被引用的路徑。如果是一些比較簡(jiǎn)單的內(nèi)存泄漏情況,僅僅看這里就大概能猜到是哪里導(dǎo)致了內(nèi)存泄漏。
但如果是比較復(fù)雜的情況,還是推薦使用MAT工具(Memory Analyzer)來(lái)繼續(xù)分析比較好。
(3)使用Memory Analyzer(MAT)來(lái)分析內(nèi)存泄漏
MAT是Eclipse出品的一個(gè)插件,當(dāng)然也有獨(dú)立的版本。下載鏈接:MAT下載地址
在這里先提醒一下:MAT并不會(huì)準(zhǔn)確地告訴我們哪里發(fā)生了內(nèi)存泄漏,而是會(huì)提供一大堆的數(shù)據(jù)和線索,我們需要根據(jù)自己的實(shí)際代碼和業(yè)務(wù)邏輯去分析這些數(shù)據(jù),判斷到底是不是真的發(fā)生了內(nèi)存泄漏。
MAT支持對(duì)標(biāo)準(zhǔn)格式的hprof文件進(jìn)行內(nèi)存分析,所以,我們要先在Android Studio里先把Java heap文件轉(zhuǎn)成標(biāo)準(zhǔn)格式的hprof文件,具體步驟如下:
點(diǎn)擊左側(cè)的capture,選擇對(duì)應(yīng)的文件,并右鍵選擇“Export to standard .hprof”導(dǎo)出標(biāo)準(zhǔn)的hprof文件:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
導(dǎo)出標(biāo)準(zhǔn)的hprof文件后,在MAT工具里導(dǎo)入,則看到以下界面:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
MAT中提供了非常多的功能,這里我們只要學(xué)習(xí)幾個(gè)最常用的就可以了。上圖那個(gè)餅狀圖展示了最大的幾個(gè)對(duì)象所占內(nèi)存的比例,這張圖中提供的內(nèi)容并不多,我們可以忽略它。在這個(gè)餅狀圖的下方就有幾個(gè)非常有用的工具了。
Histogram:直方圖,可以列出內(nèi)存中每個(gè)對(duì)象的名字、數(shù)量以及大小。
Dominator Tree:會(huì)將所有內(nèi)存中的對(duì)象按大小進(jìn)行排序,并且我們可以分析對(duì)象之間的引用結(jié)構(gòu)。
1)Dominator Tree

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
從上圖可以看到右邊存在著3個(gè)參數(shù)。Retained Heap表示這個(gè)對(duì)象以及它所持有的其它引用(包括直接和間接)所占的總內(nèi)存,因此從上圖中看,前兩行的Retained Heap是最大的,分析內(nèi)存泄漏時(shí),內(nèi)存最大的對(duì)象也是最應(yīng)該去懷疑的。
另外大家應(yīng)該可以注意到,在每一行的最左邊都有一個(gè)文件型的圖標(biāo),這些圖標(biāo)有的左下角帶有一個(gè)紅色的點(diǎn),有的則沒有。帶有紅點(diǎn)的對(duì)象就表示是可以被GC Roots訪問(wèn)到的,
可以被GC Root訪問(wèn)到的對(duì)象都是無(wú)法被回收的。那么這就可以說(shuō)明所有帶紅色的對(duì)象都是泄漏的對(duì)象嗎?當(dāng)然不是,因?yàn)橛行?duì)象系統(tǒng)需要一直使用,本來(lái)就不應(yīng)該被回收。
如果發(fā)現(xiàn)有的對(duì)象右邊有寫著System Class,那么說(shuō)明這是一個(gè)由系統(tǒng)管理的對(duì)象,并不是由我們自己創(chuàng)建并導(dǎo)致內(nèi)存泄漏的對(duì)象。
根據(jù)我們?cè)贏ndroid studio的Java heap文件的提示,TestActivity對(duì)象有可能發(fā)生了內(nèi)存泄漏,于是我們直接在上面搜TestActivity(這個(gè)搜索功能也是很強(qiáng)大的):
左邊的inspector可以查看對(duì)象內(nèi)部的各種信息:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
當(dāng)然,如果你覺得按照默認(rèn)的排序方式來(lái)查看不方便,你可以自行設(shè)置排序的方式:
Group by class
Group by class loader
Group by package

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
從上圖可以看出,我們搜出了3個(gè)TestActivity的對(duì)象,一般在退出某個(gè)activity后,就結(jié)束了一個(gè)activity的生命周期,應(yīng)該會(huì)被GC正常回收才對(duì)的。通常情況下,一個(gè)activity應(yīng)該只有1個(gè)實(shí)例對(duì)象,但是現(xiàn)在居然有3個(gè)TestActivity對(duì)象存在,說(shuō)明之前的操作,產(chǎn)生了3個(gè)TestActivity對(duì)象,并且無(wú)法被系統(tǒng)回收掉。
接下來(lái)繼續(xù)查看引用路徑。
對(duì)著TestActivity對(duì)象點(diǎn)擊右鍵 -> Merge Shortest Paths to GC Roots(當(dāng)然,這里也可以選擇Path To GC Roots) -> exclude all phantom/weak/soft etc. references
為什么選擇exclude all phantom/weak/soft etc. references呢?因?yàn)槿跻玫仁遣粫?huì)阻止對(duì)象被垃圾回收器回收的,所以我們這里直接把它排除掉

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
接下來(lái)就能看到引用路徑關(guān)系圖了:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
從上圖可以看出,TestActivity是被this
0又被callback所引用,接著它又被Message中一串的next所引用...到這里,我們就已經(jīng)分析出內(nèi)存泄漏的原因了,接下來(lái)就是去改善存在問(wèn)題的代碼了。
2)Histogram

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
這里是把當(dāng)前應(yīng)用程序中所有的對(duì)象的名字、數(shù)量和大小全部都列出來(lái)了,那么Shallow Heap又是什么意思呢?就是當(dāng)前對(duì)象自己所占內(nèi)存的大小,不包含引用關(guān)系的。
上圖當(dāng)中,byte[]對(duì)象的Shallow Heap最高,說(shuō)明我們應(yīng)用程序中用了很多byte[]類型的數(shù)據(jù),比如說(shuō)圖片。可以通過(guò)右鍵 -> List objects -> with incoming references來(lái)查看具體是誰(shuí)在使用這些byte[]。
當(dāng)然,除了一般的對(duì)象,我們還可以專門查看線程對(duì)象的信息:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
Histogram中是可以顯示對(duì)象的數(shù)量的,比如說(shuō)我們現(xiàn)在懷疑TestActivity中有可能存在內(nèi)存泄漏,就可以在第一行的正則表達(dá)式框中搜索“TestActivity”,如下所示:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
接下來(lái)對(duì)著TestActivity右鍵 -> List objects -> with outgoing references查看具體TestActivity實(shí)例
注:
List objects -> with outgoing references :表示該對(duì)象的出節(jié)點(diǎn)(被該對(duì)象引用的對(duì)象)
List objects -> with incoming references:表示該對(duì)象的入節(jié)點(diǎn)(引用到該對(duì)象的對(duì)象)
如果想要查看內(nèi)存泄漏的具體原因,可以對(duì)著任意一個(gè)TestActivity的實(shí)例右鍵 -> Merge Shortest Paths to GC Roots(當(dāng)然,這里也可以選擇Path To GC Roots) -> exclude all phantom/weak/soft etc. references,如下圖所示:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
從這里可以看出,Histogram和Dominator Tree兩種方式下操作都是差不多的,只是兩種統(tǒng)計(jì)圖展示的側(cè)重點(diǎn)不太一樣,實(shí)際操作中,根據(jù)需求選擇不同的方式即可。
3)兩個(gè)hprof文件的對(duì)比
為了排查內(nèi)存泄漏,經(jīng)常會(huì)需要做一些前后的對(duì)比。下面簡(jiǎn)單說(shuō)一下兩種對(duì)比方式:
1.直接對(duì)比
工具欄最右邊有個(gè)“Compare to another heap dump”的按鈕,只要點(diǎn)擊,就可以生成對(duì)比后的結(jié)果。(注意,要先在MAT中打開要對(duì)比的hprof文件,才能選擇對(duì)比的文件):

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
2.添加到campare basket里對(duì)比
在window菜單下面選擇compare basket:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
在文件的Histogram view模式下,在navigation history下選擇add to compare basket:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
然后就可以通過(guò) Compare Tables 來(lái)進(jìn)行對(duì)比了:

程序員Android 轉(zhuǎn)于網(wǎng)絡(luò)
七、總結(jié)
最后,還是要再次提醒一下,工具是死的,人是活的,MAT也沒有辦法保證一定可以將內(nèi)存泄漏的原因找出來(lái),還是需要我們對(duì)程序的代碼有足夠多的了解,知道有哪些對(duì)象是存活的,以及它們存活的原因,然后再結(jié)合MAT給出的數(shù)據(jù)來(lái)進(jìn)行具體的分析,這樣才有可能把一些隱藏得很深的問(wèn)題原因給找出來(lái)。
參考鏈接:https://zhuanlan.zhihu.com/p/27593816
友情推薦:
至此,本篇已結(jié)束。轉(zhuǎn)載網(wǎng)絡(luò)的文章,小編覺得很優(yōu)秀,歡迎點(diǎn)擊閱讀原文,支持原創(chuàng)作者,如有侵權(quán),懇請(qǐng)聯(lián)系小編刪除,歡迎您的建議與指正。同時(shí)期待您的關(guān)注,感謝您的閱讀,謝謝!
點(diǎn)個(gè)在看,方便您使用時(shí)快速查找!
