Android 性能優(yōu)化之旅2--內(nèi)存分析工具的使用(上)

先給出一個內(nèi)存泄漏的栗子

我們在項目中經(jīng)常會創(chuàng)建一些工具類占业,例如獲取屏幕信息的、SharePreference惦费、圖片壓縮等工具類趟脂,而且我們往往寫成單利泰讽,例如下面的CommonUtils所示。(防止內(nèi)存泄漏就應該使用Application Context)

    /**
     * 測試內(nèi)存泄漏
     */
    public class CommonUtils {

        private static CommonUtils sInstance;
        private Context mContext;

        private CommonUtils(Context context) {
            mContext = context;
        }

        public static CommonUtils getInstance(Context context) {
            if (sInstance == null) {
                sInstance = new CommonUtils(context);
            }
            return sInstance;
        }
    }

在Activity中昔期,我們經(jīng)常這樣使用:

    public class LeakActivity extends AppCompatActivity {

        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_leak);

            CommonUtils.getInstance(this);
        }
    }

這就是很典型的單例模式導致內(nèi)存對象無法釋放而導致Activity內(nèi)存泄露已卸,因為CommonUtils持有Activity的引用,導致Activity不能被正撑鹨唬回收累澡。即Destroy之后還存在。

我們先通過這一個簡單的栗子來演示一遍工具的使用般贼,然后總結一下:在不知道哪里發(fā)生內(nèi)存泄漏的時候愧哟,我們應該如何利用工具提供的線索去分析惑申。

Android Studio Android Monitor的使用

首先運行我們的項目,然后打開Android Monitor翅雏。如下圖所示:

Android Monitor.png

Android Monitor可以看到手機的一些信息圈驼,我們目前最關心的是Memory這一板塊。這里可以看到我們APP進程的空閑內(nèi)存和已經(jīng)分配的內(nèi)存望几,如圖右邊所示绩脆。在圖的前面部分我們可以看到分配內(nèi)存突然減少,這就是GC在回收垃圾了橄抹。

內(nèi)存抖動:當內(nèi)存發(fā)生很頻繁地起伏的時候靴迫,這就是內(nèi)存抖動,這時候就要注意是不是內(nèi)存泄漏引起的了楼誓。

左上角有三個小工具玉锌,這里先介紹前面兩個,第一個是手動觸發(fā)GC疟羹,第二個是拍堆內(nèi)存快照主守。

工具欄.png

我們手動觸發(fā)GC多幾次,等到內(nèi)存穩(wěn)定下來的時候榄融,然后拍快照参淫,這時候AS會自動拉去hprf文件并且打開。然后我們旋轉屏幕愧杯,觸發(fā)內(nèi)存泄漏涎才,然后再重復這樣的操作。

hprof文件.png

我們可以通過選擇查看APP力九、Image耍铜、Zygote的內(nèi)存信息,也可以選擇按照包名來展示跌前。這里需要解釋的參數(shù)有:單位是byte

Total Count:對象的總個數(shù)棕兼。
Heap Count:對象在堆內(nèi)存中的個數(shù)。
Sizeof:對象本身的大小舒萎。
Shallow Size:所有該類的對象的大小總和程储。
Retained Size:所有該類的對象釋放的時候(包括引用了該類的對象的釋放)釋放的總內(nèi)存之和蹭沛。
Depth:對象被引用的深度臂寝,如果沒有被引用,則深度為0摊灭;直接引用咆贬,深度為1,如此類推帚呼。
Dominating Size:對象管轄的內(nèi)存大小掏缎。

下面我們進行分析皱蹦,按照包名分類展示,找到我們的LeakActivity眷蜈,可以在右邊看到有幾個LeakActivity實例沪哺。在下面我們可以看到LeakActivity的引用樹。在藍色的部分就要注意了酌儒,可以看大我們的CommonUtils通過mContext直接引用了LeakActivity辜妓,藍色高亮部分一般代表內(nèi)存泄漏。我們也可以通過點擊右邊的Analyzer Tasks右上角的綠色三角形來進行自動分析忌怎。

其實我們可以直接看Activity的數(shù)量(2)就可以推測出來是否有內(nèi)存泄漏了籍滴。

內(nèi)存分析.png
延伸

我們多旋轉幾次,內(nèi)存里面是會有多個Activity實例的榴啸,但是一旦內(nèi)存比較緊張的時候孽惰,只會保留第0個和最后創(chuàng)建的那個。
中間的幾個Activity會被回收鸥印,因為并沒有被CommonUtil引用勋功。

CommonUtil與Activity的生命周期不一致,不一致的時候有可能造成泄漏库说。

實際項目比較復雜酝润,需要引用樹一個一個分析,引用是否合理璃弄,然后看代碼要销。工具只是提供線索。

MAT的使用

MAT是Memory Analyzer Tools的簡稱夏块,是專門用于Java內(nèi)存分析的工具疏咐,是Eclipse的插件,也可以單獨使用脐供,可以從官網(wǎng)下載浑塞。

首先我們要把AS生成的hprof文件轉為為標準的文件,當然政己,如果是ADT的話酌壕,可以直接拿來用。如下圖示:

轉換.png

然后在MAT中打開:我們一般選擇內(nèi)存泄漏分析報告歇由。

MAT.png

這里可以看到一些有問題的報告卵牍,例如Resources類被系統(tǒng)的類加載器加載進來了,并且占用了很大內(nèi)存空間沦泌。但是一般這個界面沒有什么卵用糊昙。

報告.png

下面這個界面比較重要:

Histogram:Lists number of instances per class
內(nèi)存中對象個數(shù)和大小,最主要看這個谢谦,其余都是看大概释牺。

Dominator Tree: List the biggest objects and what they keep alive.
列舉大對象萝衩,同時它們依賴什么,是否還存在没咙。

Overview.png

這個界面可以看到跟AS差不多的東西猩谊,這里不詳細介紹。

內(nèi)存分析.png

我們先回顧一下GC回收的算法:
以”GC Roots”的對象作為起始點向下搜索祭刚,搜索形成的路徑稱為引用鏈预柒,當一個對象到GC Roots沒有任何引用鏈相連(即不可達的),則該對象被判定為可以被回收的對象袁梗,反之不能被回收宜鸯。也寫出了內(nèi)存泄露的原因:對象無用了,但仍然可達(未釋放)遮怜,垃圾回收器無法回收淋袖。那些相互引用而不能被回收的就是內(nèi)存泄漏的對象。

我們搜索LeakActivity锯梁,然后點擊右鍵即碗,選擇Merge Shortest Path to GC Roots,選擇exclude all phantom/weak/soft etc.references, 意思是查看排除虛引用/弱引用/軟引用等的引用鏈 (這些引用最終都能夠被GC干掉陌凳,所以排除)剥懒。最后可以看到還有兩個對象引用了LeakActivity,其中輸入法是系統(tǒng)的BUG合敦。

這里也可以看到該對象被誰引用了初橘,或者引用了誰,incmming outcomming

內(nèi)存分析1.png
快照的對比

當我們懷疑執(zhí)行了某一個動作導致APP卡慢的時候充岛,可以在這個動作之前先拍一個快照保檐,執(zhí)行動作之后再拍一個,然后利用MAT進行對比崔梗。

在Navigation History界面中夜只,選中 History add to compare basket,點擊右上角的紅色嘆號執(zhí)行對比分析蒜魄,通過對比分析扔亥,看看執(zhí)行動作前后對象的內(nèi)存分配、數(shù)量差異也可以發(fā)現(xiàn)問題谈为。

對比.png

最后旅挤,其實我們也可以直接通過Android Monitor的內(nèi)存報告,看View以及Activity的數(shù)量來進行預判:
當app退出的時候峦阁,這個進程里面所有的對象應該就都被回收了谦铃,尤其是很容易被泄露的(View耘成,Activity)是否還內(nèi)存當中榔昔。
可以讓app退出以后驹闰,查看系統(tǒng)該進程里面的所有的View、Activity對象是否為0.
工具:使用AndroidStudio--AndroidMonitor--System Information--Memory Usage查看Objects里面的views和Activity的數(shù)量是否為0.

內(nèi)存報告.png

總結

往往做項目的時候情況非常復雜撒会,或者項目做得差不多了想起來要性能優(yōu)化檢查下內(nèi)存泄露嘹朗。

如何找到項目中存在的內(nèi)存泄露的這些地方呢?

1.確定是否存在內(nèi)存泄露

1)Android Monitors的內(nèi)存分析
    最直觀的看內(nèi)存增長情況诵肛,知道該動作是否發(fā)生內(nèi)存泄露屹培。(內(nèi)存抖動)
    動作發(fā)生之前:GC完后內(nèi)存1.4M; 動作發(fā)生之后:GC完后內(nèi)存1.6M

2)使用MAT內(nèi)存分析工具
MAT分析heap的總內(nèi)存占用大小來初步判斷是否存在泄露
Heap視圖中有一個Type叫做data object,即數(shù)據(jù)對象怔檩,也就是我們的程序中大量存在的類類型的對象褪秀。
在data object一行中有一列是“Total Size”,其值就是當前進程中所有Java數(shù)據(jù)對象的內(nèi)存總量薛训,
一般情況下媒吗,這個值的大小決定了是否會有內(nèi)存泄漏。
我們反復執(zhí)行某一個操作并同時執(zhí)行GC排除可以回收掉的內(nèi)存乙埃,注意觀察data object的Total Size值闸英,
正常情況下Total Size值都會穩(wěn)定在一個有限的范圍內(nèi),也就是說由于程序中的的代碼良好介袜,沒有造成對象不被垃圾回收的情況甫何。
反之如果代碼中存在沒有釋放對象引用的情況,隨著操作次數(shù)的增多Total Size的值會越來越大遇伞。

那么這里就已經(jīng)初步判斷這個操作導致了內(nèi)存泄露的情況辙喂。

2.先找懷疑對象(哪些對象屬于泄露的)

MAT對比操作前后的hprof來定位內(nèi)存泄露是泄露了什么數(shù)據(jù)對象。(這樣做可以排除一些對象鸠珠,不用后面去查看所有被引用的對象是否是嫌疑)
快速定位到操作前后所持有的對象哪些是增加了(GC后還是比之前多出來的對象就可能是泄露對象嫌疑犯)
技巧:Histogram中還可以對對象進行Group加派,比如選擇Group By Package更方便查看自己Package中的對象信息。

3.MAT分析hprof來定位內(nèi)存泄露的原因所在跳芳。(哪個對象持有了上面懷疑出來的發(fā)生泄露的對象)
1)Dump出內(nèi)存泄露“當時”的內(nèi)存鏡像hprof芍锦,分析懷疑泄露的類;
2)把上面2得出的這些嫌疑犯一個一個排查個遍飞盆。步驟:

    (1)進入Histogram娄琉,過濾出某一個嫌疑對象類
    (2)然后分析持有此類對象引用的外部對象(在該類上面點擊右鍵List Objects--->with incoming references)
    (3)再過濾掉一些弱引用、軟引用吓歇、虛引用孽水,因為它們遲早可以被GC干掉不屬于內(nèi)存泄露
       (在類上面點擊右鍵Merge Shortest Paths to GC Roots--->exclude all phantom/weak/soft etc.references)
    (4)逐個分析每個對象的GC路徑是否正常
       此時就要進入代碼分析此時這個對象的引用持有是否合理,這就要考經(jīng)驗和體力了城看!
       (比如上課的例子中:旋轉屏幕后MainActivity有兩個女气,肯定MainActivity發(fā)生泄露了,
         那誰導致他泄露的呢测柠?原來是我們的CommonUtils類持有了旋轉之前的那個MainActivity他炼鞠,
         那是否合理缘滥?結合邏輯判斷當然不合理,由此找到內(nèi)存泄露根源是CommonUtils類持有了該MainActivity實例造成的谒主。
         怎么解決朝扼?罪魁禍首找到了,怎么解決應該不難了霎肯,不同情況解決辦法不一樣擎颖,要靠你的智慧了。)
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末观游,一起剝皮案震驚了整個濱河市搂捧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌懂缕,老刑警劉巖异旧,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異提佣,居然都是意外死亡吮蛹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門拌屏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來潮针,“玉大人,你說我怎么就攤上這事倚喂∶颗瘢” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵端圈,是天一觀的道長焦读。 經(jīng)常有香客問我,道長舱权,這世上最難降的妖魔是什么矗晃? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮宴倍,結果婚禮上张症,老公的妹妹穿的比我還像新娘。我一直安慰自己鸵贬,他們只是感情好俗他,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著阔逼,像睡著了一般兆衅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天羡亩,我揣著相機與錄音摩疑,去河邊找鬼。 笑死夕春,一個胖子當著我的面吹牛未荒,可吹牛的內(nèi)容都是我干的专挪。 我是一名探鬼主播及志,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寨腔!你這毒婦竟也來了速侈?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤迫卢,失蹤者是張志新(化名)和其女友劉穎倚搬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乾蛤,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡每界,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了家卖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眨层。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖上荡,靈堂內(nèi)的尸體忽然破棺而出趴樱,到底是詐尸還是另有隱情,我是刑警寧澤酪捡,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布叁征,位于F島的核電站,受9級特大地震影響逛薇,放射性物質(zhì)發(fā)生泄漏捺疼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一永罚、第九天 我趴在偏房一處隱蔽的房頂上張望帅涂。 院中可真熱鬧,春花似錦尤蛮、人聲如沸媳友。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽醇锚。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間焊唬,已是汗流浹背恋昼。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赶促,地道東北人液肌。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像鸥滨,于是被迫代替她去往敵國和親嗦哆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容