Android 記憶體剖析 之 MAT講解
簡介
移動平臺上的開發和記憶體管理緊密相關。儘管隨著科技的進步,現今移動裝置上的記憶體大小已經達到了低端桌面裝置的水平,但是現今開發的應用程式對記憶體的需求也在同步增長。主要問題出在裝置的螢幕尺寸上-解析度越高需要的記憶體越多。熟悉Android平臺的開發人員一般都知道垃圾回收器並不能徹底杜絕記憶體洩露問題,對於大型應用而言,記憶體洩露對效能產生的影響是難以估量的,因此開發人員必須要有記憶體分析的能力。本文介紹一些有用的工具,以及如何使用它們來檢測這些關鍵的記憶體洩露問題。
工具
有很多工具可以用來幫助檢測記憶體洩露問題,這裡列舉了一些,並附上一點相應的介紹:
工具名稱: |
簡介: |
DDMS (Dalvik除錯監視伺服器) |
和Android一起推出的除錯工具(android sdk的tools目錄下就有)。提供埠轉發服務、截圖、執行緒監控、堆dump、logcat、程序和無線狀態監控以及一些其他功能。可以通過”./ddms”命令啟動該工具,同時它也被整合在ADT中,安裝以後切到DDMS檢視即可。 |
MAT (記憶體分析工具) |
快速的Java堆分析器,該工具可以檢測到記憶體洩露,降低記憶體消耗,它有著非常強大的解析堆記憶體空間dump能力。還有很多其他功能無法在這裡一一列出,可以安裝一下MAT的eclipse外掛試試,要活的更多詳細資訊請點選 |
術語介紹
記憶體分析涉及到很多專用術語,他們在本文中將頻繁出現,這裡給出每個術語的定義:
術語: |
定義: |
堆大小 |
分配給Java堆的記憶體,在Android平臺,這些記憶體都是針對每個Activity分配的(這還取決於裝置) |
堆轉儲檔案 |
一個包含了堆中資訊的二進位制檔案 |
支配樹(Dominator Tree) |
一個用來圖形化展示物件之間關係的工具,詳情請參考wiki |
記憶體洩露 |
記憶體洩露是指有個引用指向一個不再被使用的物件,導致該物件不會被垃圾回收器回收。如果這個物件有個引用指向一個包括很多其他物件的集合,就會導致這些物件都不會被垃圾回收。因此,需要牢記,垃圾回收無法杜絕記憶體洩露。 |
GC根 |
GC根是指那些假設可達的物件。 通常包括所有當前棧和系統的類載入器載入的類中引用的物件。(【譯者注】棧裡引用的物件是指當前執行的方法裡的區域性變數指向的物件,系統載入器載入的類引用的物件包括類的靜態屬性引用的物件) |
記憶體洩露
記憶體洩露是指有個引用指向一個不再被使用的物件,導致該物件不會被垃圾回收器回收。如果這個物件有個引用指向一個包括很多其他物件的集合,就會導致這些物件都不會被垃圾回收。
圖. 1
淺堆(Shallow Heap)被一個物件直接消耗的記憶體
例子:
public final class myObject { // 頭部: 8位元組
private int valueA; // 整型: 4位元組
private int valueB; // 整型: 4位元組
private char valueC[]; // 字元型陣列: 4位元組
}
一個myObject物件共消耗20個位元組保留堆(Retained Heap)
因釋放了某個物件後,可以釋放的所有記憶體總和。
GC前:GC即將回收物件A
圖. 2
GC後:回收物件A(300位元組)從而導致回收物件B(50位元組)和C(50位元組),同時釋放了物件B以後物件D也會被回收(100位元組),因此回收物件A就可以釋放500位元組的記憶體,所謂保留隊正式這些物件直接佔用的淺堆總和。
圖 3
檢測記憶體洩露
有好幾種方法可以發現記憶體洩露,本小節介紹其中幾種。Logcat輸出的log
第一種發現記憶體洩露的方法是檢查logcat輸出的log。當垃圾回收器工作時,可以在Logcat中看到它的訊息,這訊息長的樣子類似於:
D/dalvikm( 14302): GC_CONCURRENT freed 2349K, 65% free 3246K/9551K, external 4703K/5261K, paused 2ms+2ms
這條訊息的第一個部分說明該訊息產生的原因,一共有四種類型:
GC_CONCURRENT |
當堆變得很大,防止出現堆溢位異常時產生 |
GC_FOR_MALLOC |
如果GC_CONCURENT型別的操作沒有及時執行,並且應用程式還需要分配更多記憶體時產生。 |
GC_EXTERNAL_ALLOC |
在Android3.0 (Honeycomb)以前,釋放通過外部記憶體(externel memory, 通過JNI程式碼中malloc分配得到的記憶體)時產生。Android3.0和更高版本中不再有這種型別的記憶體分配了。 |
GC_EXPLICIT |
呼叫System.gc時產生 |
“freed 2349K,” – 說明釋放了多少記憶體.
“65% free 3246K/9551K” – 65%表示目前可分配記憶體佔比例,3426K表示當前活動物件所佔記憶體,9551K表示堆大小。
“external 4703K/5261K” – indicates external memory allocation, how much external memory the app has allocated and the soft limit of allocation.說明外部記憶體的分配,已經分配了多少以及能夠分配的上限。
“paused 2ms+2ms” –說明GC執行完成需要的時間。
有了這些資訊,我們就可以知道GC執行幾次以後有沒有成功釋放出一些記憶體,如果分配出去的記憶體在這幾個週期中持續增加,那麼很明視訊記憶體在記憶體洩露。下面的例子中就是存在記憶體洩漏時的Log。(【譯者注】圖片有點不清楚,但是大概可以看出來GC運行了好多次,可分配記憶體比例反而從47%降到45%了)
圖. 4
OutOfMemoryError 異常
當可用記憶體耗盡時,將會丟擲OutOfMemoryError異常(OOM),此異常的出現說明有可能存在記憶體洩露,但這個辦法並不是萬無一失的,因為有可能開發者就是想分配一塊大記憶體(比如處理bitmap時),並且這一大塊記憶體的大小超出了平臺限制。但是此異常的出現至少說明開發人員必須要重新考慮自己程式碼對記憶體的使用方法是不是有問題。跟蹤記憶體分配情況
成功的發現記憶體洩露問題以後,就應該查詢根源在哪裡了,有兩個工具可以用來輔助分析問題根源所在。
DDMS
DDMS是一個強大的工具,他可以提供有很價值的資訊,它還可以生成一個HPROF dump檔案,該檔案可以使用MAT開啟分析。在Eclipse中開啟DDMS,只需安裝ADT外掛以後開啟DDMS檢視即可。
圖. 5
可以看到,介面相當直觀,不需要什麼詳細的說明,但有兩個標籤頁值得注意。第一個是“Allocation Tracker”,它用來顯示當前記憶體分配的詳細情況,可以看到每種資料型別(基本資料型別或者自定義類)的記憶體分配大小,甚至可以看到是哪個原始碼檔案的哪一行程式碼分配出去的。圖. 6
第二個是“Heap”,它可以顯示每個類有多少個物件,每個物件分配了多少記憶體。(【譯者注】想要使用heap標籤頁work,要在左邊視窗先選擇自己想要監控的應用的package name,然後點選上面的工具欄點選那個綠色的小圖示,從左往右數第二個,開啟這項功能)。圖. 7
獲得HPROF dump檔案很容易,連線裝置,啟動想要監控的應用,執行一會,點選左邊裝置視窗上方工具欄的“Dump HPROF File”按鈕(從左往右數第三個)。如果Eclipse有安裝MAT外掛的話,則會自動開啟此dump檔案。記憶體分析工具 (MAT)
MAT是個強大的記憶體分析工具,可以單獨使用也可以作為Eclipse的外掛(【譯者注】這個工具不在ADT中,可以在http://www.eclipse.org/mat/downloads.php下載,有stand-alone版本和Eclipse安裝的update URL),這兩種使用方法唯一的區別就是如何開啟一個HPROF檔案。獨立版本需要一個打包好的HPROF檔案。我們可以使用android adk自帶的hprof-conv工具(在android sdk的tools目錄下)打包。如果使用Eclipse外掛版的MAT則不需要,直接在Eclipse中開啟MAT檢視即可。
概述
當開啟HPROF檔案後,可以看到一個Overview介面,它由以下元素構成:
- Overview標籤頁,提供一個概覽介面。
- Histogram檢視,它提供每個類的物件統計(本文稍後詳述)
- 支配樹(Dominator Tree),提供程式中最佔記憶體的物件 (described later in the article).
- 物件查詢語言(Object Query Language Studio), 用來寫MAT查詢的工具.
- 專家系統測試(Expert System Test) –
- 堆Dump概況(Heap Dump Overview) –提供堆dump檔案的詳細資訊
- 疑似洩露點(Leak Suspects) – 提供記憶體洩露疑點佔用記憶體大小,被誰載入的,以及型別等詳細資訊。
- Top Components – 提供佔記憶體最多的物件資訊,還包括可能的記憶體浪費資訊.
- 查詢瀏覽器(Query Browser) – 提供很多很有用的查詢,有助於記憶體分析,本文將會介紹最有用的那些查詢。根據地址查詢物件 – 可以根據提供的一個地址查詢某個特定的物件.
- 物件列表(List Objects) – 顯示應用中所有物件,以及這些物件持有哪些其他物件和被哪些其他物件持有,(MAT會提示查詢哪一個物件)。
- 根據類顯示物件(Show Objects by Class) – 列出每個類有多少物件.
- 到GC根節點的路徑(Path to GC Roots) – 顯示到根節點的引用路徑 (有好多過濾選項).
- 合併到GC根節點的最短路徑(Merge Shortest Paths to GC Roots) –找到從GC根節點到一個物件或一組物件的共同路徑。
- 即時支配(Immediate Dominators) – Finds and aggregates on a class level all objects dominating a given set of objects. 在給定的一組物件中,從類的層面上查詢並聚合所有支配關係。(【譯者注】好吧,我覺得實在有必要說一下支配的意思,支配在計算機的控制流理論中意思是假如說從起始節點到節點B的所有路徑都經過節點A,則節點A支配節點B。在垃圾回收理論中應該是指從某個物件在另外一個物件的保留堆中)
- 顯示保留集合(Show Retained Set) – 計算一個物件的保留堆大小.
- 餅圖 – 顯示持有記憶體最大的物件
- 直方圖 – 顯示每個類的物件數量
- 支配樹 – 列出所有物件,並按照物件持有的保留堆大小排序
- 檢查器 – 選擇一個物件,並顯示其詳細資訊
圖. 8
直方圖(Histogram)
MAT最有用的工具之一,它可以列出任意一個類的例項數。查詢記憶體洩露或者其他記憶體方面問題是,首先看看最有可能出問題的類,這個類有多少個例項是個比較好的選擇。它支援使用正則表示式來查詢某個特定的類,還可以計算出該類所有物件的保留堆最小值或者精確值。
- 計算保留堆大小
a) 計算保留堆最小值(Calculate Minimum Retained Size) –計算保留堆最小值,並顯示在表格中.
b) 計算保留堆精確值(Calculate Precise Retained Size) – 計算保留堆精確值(這個過程需要一點時間) 並且顯示在表格中. - 正則表示式(Regex pattern) – 讓使用者查詢某個特定的物件類
圖. 9
另外,當選擇了某條顯示條目後,可以通過右擊彈出選單。在診斷記憶體相關問題時,這個選單是個非常重要的工具。如果開發者懷疑這裡有個記憶體洩露,可以通過選單直接檢視該類的物件持有哪些其他物件,當然,MAT支援過濾查詢結果(比如說限制被持有物件的型別)。查詢結果出來時,列表通過另外一個有用的工具-”Path toGC Roots”-展示給開發人員。它支援多種過濾選項,比如說排除弱引用-這是最常見的一個選項,因為當GC執行時,被弱引用持有的物件會被GC直接回收,所以這種物件是不會造成記憶體洩露的,一般直接把這種資訊排除。如果MAT預定義的查詢不能滿足使用者需求的話,它還支援自己定製查詢,定製的自由度非常大,擁有無限的可能。本文稍後會介紹如何高效的定製查詢。
圖. 10
圖 11
支配樹(Dominator Tree)
支配樹可以算是MAT中第二有用的工具,它可以將所有物件按照保留堆大小排序顯示。使用者可以直接在“Overview”選項頁中點選“Dominator Tree”進入該工具,也可以在上面提到的選單中選擇“immediate dominators”進入該工具。前者顯示dump檔案中所有的物件,後者會從類的層面上查詢並聚合所有支配關係。支配樹有以下重要屬性:
- 屬於X的子樹的物件表示X的保留物件集合。
- 如果X是Y的持有者,那麼X的持有者也是Y的持有者。
- 在支配樹中表示持有關係的邊並不是和程式碼中物件之間的關係直接對應,比如程式碼中X持有Y,Y持有Z,在支配樹中,X的子樹中會有Z。
這三個屬性對於理解支配樹而言非常重要,一個熟練的開發人員可以通過這個工具快速的找出持有物件中哪些是不需要的以及每個物件的保留堆。
圖. 12
查詢(Queries)
查詢是用來檢查物件樹的基本工具,記憶體分析就是在許多物件中查詢不希望看到的引用關係的過程-這件事聽上去容易做起來難。如果可以過濾這些物件和應用關係的話可以使這項複雜的運動簡單不少。一個開發人員想要成功的除錯記憶體問題,必須掌握兩個關鍵點。第一個是對自己的應用充分了解,如果對自己應用程式中的物件之間的關係不夠了解的話,是不能找到記憶體問題的。第二個是掌握過濾和查詢的技巧。如果開發者知道物件結構,而且也可以快速的找到想要的東西,那麼找到那些異常狀況將會變得容易一些。這裡列出MAT工具所有內建的查詢:
(【譯者注】下面表格中的前兩列都是MAT工具中選單的名稱)
查詢: |
選項: |
描述: |
List objects |
With Outgoing References |
顯示選中物件持有哪些物件. |
With Incoming References |
顯示選中物件被哪些物件持有。[如果一個類有很多不需要的例項,那麼可以找到哪些物件持有該物件,讓這個物件沒法被回收] |
|
Show object by class |
With Outgoing References |
顯示選中物件持有哪些物件, 這些物件按類合併在一起排序 |
With Incoming References |
顯示選中物件被哪些物件持有.這些物件按類合併在一起排序 |
|
Path to GC Roots |
With all references |
顯示選中物件到GC根節點的引用路徑,包括所有型別引用. |
Exclude weak references |
顯示選中物件到GC根節點的引用路徑,排除了弱引用. [弱引用不會影響GC回收物件] |
|
Exclude soft references |
顯示選中物件到GC根節點的引用路徑,排除軟引用(【譯者注】軟引用持有的物件在記憶體空間足夠時,GC不回收,記憶體空間足夠時,GC回收) |
|
Exclude phantom references |
顯示選中物件到GC根節點的引用路徑,排除虛引用(【譯者注】虛引用是最弱的引用,get()總是返回null,當它的物件被GC回收時,GC將reference放在ReferenceQueue中,使用者程式碼當發現這個reference在在ReferenceQueue時就知道它持有的物件已經被回收了,這時可以做一些清理工作。《》第四版,中文版,第87頁寫到Java的finilize方法是為了物件被回收前做清理工作,但是事實上會有隱患,虛引用正是彌補) |
|
Merge Shortest Paths to GC Roots. |
選項和“Path to GC Roots”一樣 |
顯示GC根節點到選中物件的引用路徑 |
Java Basics |
References Statistics Class Loader Explorer |
顯示引用和物件的統計資訊,列出類載入器,包括定義的類 |
Customized Retained Set |
計算選中物件的保留堆,排除指定的引用 |
|
Open in Dominator Tree |
對選中物件生成支配樹 |
|
Show as Histogram |
展示任意物件的直方圖 |
|
Thread Details |
顯示執行緒的詳細資訊和屬性 |
|
Thread Overview and Stacks |
- |
|
Java Collections |
Array Fill Ratio |
輸出陣列中,非基本型別、非null物件個數佔陣列總長度的比例。 |
Arrays Grouped by Size |
顯示陣列的直方圖,按大小分組 |
|
Collection Fill Ratio |
輸出給定集合中,非基本型別、非null物件個數佔集合容量的比例。 |
|
Collections Grouped by Size |
顯示集合的直方圖,按大小分組 |
|
Extract Hash Set Values |
列出指定hash集合中的元素 |
|
Extract List Values |
列出指定LinkedList,ArrayList或Vector中的元素 |
|
Hash Entries |
展開顯示指定HashMap或Hashtable中的鍵值對 |
|
Map Collision Ratio |
輸出指定的對映集合的碰撞率 |
|
Primitive Arrays With a Constant Value |
列出基本資料型別的陣列,這些陣列是由一個常數填充的。 |
|
Leak Identification |
Component Report Top Consumers |
分析可能的記憶體浪費或者低效使用的元件,並輸出最大的那個 |
報告(Reports)
MAT自帶有一個報告生成系統,他可以自動分析dump檔案並且生成報告給使用者。第一種報告叫做“洩露疑點(Leak suspects)”,MAT分析dump檔案,檢查是否存在一些個頭大的物件被一些引用持有保持活動狀態,需要注意的是,洩露疑點並不一定是真的記憶體洩露。第二種報告叫做“頂級元件(Top Components)“,它包括可能的記憶體浪費資訊,佔用記憶體大的物件以及引用的統計資訊。此報告對記憶體優化有很大幫助。
洩露疑點報告
洩露疑點報告包括一些潛在的記憶體洩露資訊,再次強調一下,在這裡列出的物件並不一定是真正的記憶體洩露,但它仍然是檢查記憶體洩露的一個絕佳起點。報告主要包括描述和到達聚點的最短路徑, 第三部分(每種型別積累的物件)主要是從第二部分衍生出來的(根據型別排序)。
圖 13
“到聚點的最短路徑” 非常有用,它可以讓開發人員快速發現到底是哪裡讓這些物件無法被GC回收。
圖. 14
頂級元件報告
這種報告包含很多有用的資訊,特別是對那些想要降低記憶體使用的人來說。它包括涉嫌記憶體浪費的的物件,還包括引用的統計資訊。整個報告非常簡單直白,因此這裡不需要過多的詳細介紹了。
圖. 15
圖. 16使用MAT檢測記憶體洩露
本小節主要介紹如何使用MAT檢測記憶體洩露的實踐部分,因此將會提供一段會造成記憶體洩露的程式碼作為例子。
會記憶體洩露的樣例程式碼
這段程式碼主要為了闡述記憶體洩露的最主要原因,並且為使用MAT提供一些素材。最常見的記憶體洩露主要有兩種情況:第一種是一個靜態引用持有一個普通內部類的例項(非巢狀類例項),這樣會造成該例項一直處於活動狀態,不會被GC回收。然後,每次螢幕旋轉時,Activity的onCreate方法都會被執行,將會建立一個新的MainActivity物件,由於舊的引用,導致第一個Activity不會被垃圾回收。第二個案例被稱為“context洩露”,這種情況比較難以發現,因為主要問題出在將應用的context交給一個靜態屬性,當然這個引用是強引用。第一個記憶體洩露例子
這是第一個記憶體洩露例子的原始碼,它會讓第一個MainActivity類的例項無法讓GC回收。當然這裡的物件和類名稱是隨便取的,因為這僅僅是個一個例子,在真實專案的程式碼中是不會出現這種情況的。(【譯者注】因為leakInstance是靜態屬性,它在第一次建立MainActivity例項時被賦予一個內部類物件,我們知道一個內部類物件是可以無條件訪問外圍物件的所有成員的,因此這個內部類物件會持有整個MainActivity物件,導致整個MainActivity都無法被回收)1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | publicclassMainActivityextendsActivity { //靜態屬性持有非靜態內部類的例項--這麼做非常糟糕 staticMyLeakedClass leakInstance =null; @Override publicvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Static field initialization if(leakInstance ==null) leakInstance =newMyLeakedClass(); ImageView mView =newImageView(this); mView.setBackgroundResource(R.drawable.leak_background); setContentView(mView); } /* *非靜態內部類 */ classMyLeakedClass { intsomeInt; } } |
圖. 17
從第一次旋轉開始,每次都存在3mb的差異。每次旋轉後,堆的大小都漲3mb。這是有什麼東西不對勁的第一個警告,然後LogCat沒有顯示任何釋放已分配記憶體的訊號。這時就需要檢查應用程式內部記憶體的情況了,執行DDMS,獲取HPROF檔案,然後用自帶的MAT開啟。
主螢幕上的確顯示了有什麼東西導致記憶體洩露了-它大概長的和圖18差不多。
圖. 18
餅圖上顯示,有很大一部分記憶體被資原始檔佔用-這很正常,因為任何應用都有一個GUI,但是本例子只有一個資原始檔,因此問題應該應該隱藏在這裡。還有兩個FrameLayout例項(每個有3mb)需要檢查,開發者還可以沿著一些路徑還檢查記憶體洩露問題。
基於直方圖的檢查
讓我們看看直方圖,可以看到有大量記憶體被分配給了bytes和Bitmpa型別,看看一個Bitmap物件的incoming references, 結果如圖19所示:圖. 19
有三個比較大的bitmap物件,這麼看來這個本例最壞可能有兩到三個記憶體洩露。這幾個物件的記憶體大小符合LogCat的輸出,讓我們在檢查一下他們到GC根節點的路徑(剔除所有弱應用)。第一個bitmap物件看上去沒什麼問題,因為它只有一個應用指向自己,沒有被任何其他物件引用,而且它正在等著被垃圾回收器回收。
圖. 20
第二個物件的引用有些多,但是一切看上也也正常,看上去這是一個可見的bitmap – 這就是為什麼他有指向Activity和Context的引用的原因。圖 21
看來問題就出在剩下的第三個bitmap上了。它到GC根節點只有一條路徑,而且它是被“leakInstance”物件持有的,正是leakInstance物件阻止了該bitmap物件被回收。
圖. 22
同時,在路徑上還有一個MainActivity物件 – 看到MainActivity物件不奇怪,因為每次旋轉都會新建立一個Activity,讓我們看看到底發生了什麼。首先通過正則表示式過濾器在直方圖中找出MainActivity物件。
圖. 23
第一個不對勁的地方是有三個MainActivity例項。這當然不是說他們一定是記憶體洩露,但有些時候它們的生存時間倒的確比其他物件要長,所以我們看看到底是什麼阻止它被垃圾回收。要做到這一點,首先列出所有有incoming reference的物件。再檢查一下到GC根節點的路徑。圖. 24
第一個MainActivity物件有一個引用指向context和ActivityThread,因此它看上去是現在正在顯示的Activity。
圖. 25
第二個物件只有一個引用指向自己,它正等著被垃圾回收,到目前為止,一切看上去都正常的。
圖 26
現在再看第三個 – 就是它了!有個強引用指向leakInstance物件,就是它阻止了該物件被垃圾回收。
圖. 27
基於支配樹的檢查
開發者可以通過很多種方法找到記憶體洩露。本文只能介紹其中幾種,第二個要介紹的是基於支配樹檢視的。開啟HPROF檔案的支配樹檢視,按照保留堆大小進行排序。正如預料的一樣,最上面的是資源類物件,還有三個FrameLayout類的物件(每個3mb)以及一個Bitmap物件(1mb)。FrameLayout物件看上去嫌疑很大,因此我們首先檢查它們。因為支配樹已經列出了具體的物件,因此我們可以直接檢視它們到GC根節點的路徑。
圖. 28
第一個物件就是問題所在!它到GC根節點的唯一路徑正是leakInstance物件,因此它是一個洩露。
圖. 29
第二個和第三個物件分別是當前正在顯示和正在等著垃圾回收的。
圖. 30
讓我們在看看那個bitmap物件,它也有可能是一個記憶體洩露。選擇android.graphic.Bitmap,選擇顯示到GC根節點的路徑,剔除所有弱引用。
圖. 31
bitmap型別有三個物件,每個物件到GC根節點的路徑都可以檢視到,上面說的情況再次重演,三個例項中的兩個很顯然沒問題,但是第三個物件指向leakInstance,它一直都是活動狀態,不會被回收。
圖. 32
可能還有上百條路徑可以順藤摸瓜找出最終的洩露點,應該選擇哪條路徑取決於不同的開發者了,不同的開發人員有對如何分析記憶體有著不同的見解。
第二個記憶體洩露例子
第二個記憶體洩露場景發生在application context上。它將application context傳遞給一個單例模式的類,並將其作為一個屬性保留下來。這個操作將會使得MainActivity無法被垃圾回收。將context作為靜態屬性儲存也會導致同樣的結果,因此這種做法應該避免。為了避免重複羅嗦,這裡只介紹一種查詢記憶體洩露的方法。
下面是程式碼:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | publicclassMainActivity2extendsActivity { SingletonClass mSingletonClass =null; @Override publicvoidonCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mSingletonClass = SingletonClass.getInstance(this); } } classSingletonClass { privateContext mContext =null; privatestaticSingletonClass mInstance; privateSingletonClass(Context context) { mContext = context; } publicstaticSingletonClass getInstance(Context context) { if(mInstance ==null) { mInstance =newSingletonClass(context); } returnmInstance; } } |
圖. 33
概覽介面並沒有提供什麼重要資訊,因此開發人員需要繼續自己的探索。這個例子中沒有bitmap和其他資源,但是直方圖顯示這裡有很多MainActivity物件 – 檢查檢查它們也許能得到更多更有價值的訊息。
圖. 34
將手機旋轉3次,直方圖顯示這裡有4個MainActivity物件。嗯,是時候檢查是不是有哪個物件阻止它們被回收了。要做到這一點,首先列出所有有incomming refrence的物件。只需要展開檢視就很容易發現第一個物件就是當前正在顯示的Activity(他包含指向ActivityThread的引用)。
圖. 35
繼續列出其他兩個物件的到GC根節點的路徑。其中一個只有一個引用指向它自己,另外一個指向mInstance,該引用在SignletonClass中,還有一個應用指向當前顯示的Activity(從mSigletonClass)。這正是一個洩露。
圖. 36
很明顯可以看出context讓垃圾回收無法回收該物件。另外還有一個問題 – 每次建立一個Acitivity例項的時候,context都被傳遞給SingletonClass。這是個嚴重的問題,因為context引用指向一個不在需要的Activity,從而讓這個Activity保持活躍無法被回收。在比較大的專案中,這中情況可能會導致一些意象不到的行為,並且這種問題很難被檢查出來。