Android 內存剖析 之 MAT講解

簡介 php

移動平臺上的開發和內存管理緊密相關。儘管隨着科技的進步,現今移動設備上的內存大小已經達到了低端桌面設備的水平,可是現今開發的應用程序對內存的需求也在同步增加。主要問題出在設備的屏幕尺寸上-分辨率越高須要的內存越多。熟悉Android平臺的開發人員通常都知道垃圾回收器並不能完全杜絕內存泄露問題,對於大型應用而言,內存泄露對性能產生的影響是難以估量的,所以開發人員必需要有內存分析的能力。本文介紹一些有用的工具,以及如何使用它們來檢測這些關鍵的內存泄露問題。 html

工具

有不少工具能夠用來幫助檢測內存泄露問題,這裏列舉了一些,並附上一點相應的介紹: android

工具名稱: 正則表達式

簡介: 編程

DDMS (Dalvik調試監視服務器) 數組

和Android一塊兒推出的調試工具(android sdk的tools目錄下就有)。提供端口轉發服務、截屏、線程監控、堆dump、logcat、進程和無線狀態監控以及一些其餘功能。能夠經過」./ddms」命令啓動該工具,同時它也被集成在ADT中,安裝之後Eclipse切到DDMS視圖便可。 瀏覽器

MAT (內存分析工具) 服務器

快速的Java堆分析器,該工具能夠檢測到內存泄露,下降內存消耗,它有着很是強大的解析堆內存空間dump能力。還有不少其餘功能沒法在這裏一一列出,能夠安裝一下MAT的eclipse插件試試,要活的更多詳細信息請點擊這裏app

術語介紹

內存分析涉及到不少專用術語,他們在本文中將頻繁出現,這裏給出每一個術語的定義: dom

術語:

定義:

堆大小

分配給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界面,它由如下元素構成:

  1. Overview標籤頁,提供一個概覽界面。
  2. Histogram視圖,它提供每一個類的對象統計(本文稍後詳述)
  3. 支配樹(Dominator Tree),提供程序中最佔內存的對象 (described later in the article).
  4. 對象查詢語言(Object Query Language Studio), 用來寫MAT查詢的工具.
  5. 專家系統測試(Expert System Test) –
    1. 堆Dump概況(Heap Dump Overview) –提供堆dump文件的詳細信息
    2. 疑似泄露點(Leak Suspects) – 提供內存泄露疑點佔用內存大小,被誰加載的,以及類型等詳細信息。
    3. Top Components – 提供佔內存最多的對象信息,還包括可能的內存浪費信息.
  6. 查詢瀏覽器(Query Browser) – 提供不少頗有用的查詢,有助於內存分析,本文將會介紹最有用的那些查詢。根據地址查找對象 – 能夠根據提供的一個地址查找某個特定的對象.
    1. 對象列表(List Objects) – 顯示應用中全部對象,以及這些對象持有哪些其餘對象和被哪些其餘對象持有,(MAT會提示查詢哪個對象)。
    2. 根據類顯示對象(Show Objects by Class) – 列出每一個類有多少對象.
    3. 到GC根節點的路徑(Path to GC Roots) – 顯示到根節點的引用路徑 (有好多過濾選項).
    4. 合併到GC根節點的最短路徑(Merge Shortest Paths to GC Roots) –找到從GC根節點到一個對象或一組對象的共同路徑。
    5. 即時支配(Immediate Dominators) – Finds and aggregates on a class level all objects dominating a given set of objects. 在給定的一組對象中,從類的層面上查找並聚合全部支配關係。(【譯者注】好吧,我以爲實在有必要說一下支配的意思,支配在計算機的控制流理論中意思是假如說從起始節點到節點B的全部路徑都通過節點A,則節點A支配節點B。在垃圾回收理論中應該是指從某個對象在另一個對象的保留堆中)
    6. 顯示保留集合(Show Retained Set) – 計算一個對象的保留堆大小.
  7. 餅圖 – 顯示持有內存最大的對象
  8. 直方圖 – 顯示每一個類的對象數量
  9. 支配樹 – 列出全部對象,並按照對象持有的保留堆大小排序
  10. 檢查器 – 選擇一個對象,並顯示其詳細信息

圖. 8

直方圖(Histogram)

MAT最有用的工具之一,它能夠列出任意一個類的實例數。查找內存泄露或者其餘內存方面問題是,首先看看最有可能出問題的類,這個類有多少個實例是個比較好的選擇。它支持使用正則表達式來查找某個特定的類,還能夠計算出該類全部對象的保留堆最小值或者精確值。

  1. 計算保留堆大小
    a) 計算保留堆最小值(Calculate Minimum Retained Size) –計算保留堆最小值,並顯示在表格中.
    b) 計算保留堆精確值(Calculate Precise Retained Size) – 計算保留堆精確值(這個過程須要一點時間) 而且顯示在表格中.
  2. 正則表達式(Regex pattern) – 讓用戶查詢某個特定的對象類

圖. 9

另外,當選擇了某條顯示條目後,能夠經過右擊彈出菜單。在診斷內存相關問題時,這個菜單是個很是重要的工具。若是開發者懷疑這裏有個內存泄露,能夠經過菜單直接查看該類的對象持有哪些其餘對象,固然,MAT支持過濾查詢結果(好比說限制被持有對象的類型)。查詢結果出來時,列表經過另一個有用的工具-」Path toGC Roots」-展現給開發人員。它支持多種過濾選項,好比說排除弱引用-這是最多見的一個選項,由於當GC運行時,被弱引用持有的對象會被GC直接回收,因此這種對象是不會形成內存泄露的,通常直接把這種信息排除。若是MAT預約義的查詢不能知足用戶需求的話,它還支持本身定製查詢,定製的自由度很是大,擁有無限的可能。本文稍後會介紹如何高效的定製查詢。

圖. 10

圖 11

支配樹(Dominator Tree)

支配樹能夠算是MAT中第二有用的工具,它能夠將全部對象按照保留堆大小排序顯示。用戶能夠直接在「Overview」選項頁中點擊「Dominator Tree」進入該工具,也能夠在上面提到的菜單中選擇「immediate dominators」進入該工具。前者顯示dump文件中全部的對象,後者會從類的層面上查找並聚合全部支配關係。支配樹有如下重要屬性:

  1. 屬於X的子樹的對象表示X的保留對象集合。
  2. 若是X是Y的持有者,那麼X的持有者也是Y的持有者。
  3. 在支配樹中表示持有關係的邊並非和代碼中對象之間的關係直接對應,好比代碼中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時就知道它持有的對象已經被回收了,這時能夠作一些清理工做。《Java編程思想》第四版,中文版,第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;
  }
}
首先看一下這段代碼的LogCat的輸出。鏈接設備,運行logcat,啓動應用,而後旋轉幾回手機。圖17是LogCat輸出的例子:

圖. 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;
  }
}
運行了上面這個應用之後,把HPROF文件弄出來,而後用MAT打開分析,咱們熟悉的概覽界面又出現了:

圖. 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保持活躍沒法被回收。在比較大的項目中,這中狀況可能會致使一些意象不到的行爲,而且這種問題很難被檢查出來。

相關文章
相關標籤/搜索