前文講到了內存泄漏的緣由,那麼要怎麼定位內存泄漏呢?這裏列出了經常使用的分析工具及其使用方法
如下Heap Snapshot
、MAT
、Heap Viewer
、Allaction Tracking
、LeakCanary
和TraceView
資料均來源於網絡php
獲取Java堆內存詳細信息,能夠分析出內存泄漏的問題
在2.X版本中,Android Studio
使用的分析工具
點擊Monitor
即可查看CPU
,Memory
,Network
,GPU
的狀況
其打開面板以下:
該面板裏的信息能夠有三種類型:app heap/image heap/zygote heap
分別表明app堆內存信息,圖片堆內存信息,zygote
進程的堆內存信息
java
列舉了堆內存中全部的類,一下是列表中列名:android
名稱 | 意義 |
---|---|
Total Count |
內存中該類的對象個數 |
Heap Count |
堆內存中該類的對象個數 |
Sizeof |
物理大小 |
Shallow size |
該對象自己佔有內存大小 |
Retained Size |
釋放該對象後,節省的內存大小 |
當咱們點擊某個類時,右邊的B區域會顯示該類的實例化對象,這裏面會顯示有多少個實體,以及詳細信息
git
名稱 | 意義 |
---|---|
depth |
深度 |
Shallow Size |
對象自己內存大小 |
Dominating Size |
管轄的內存大小 |
當你點擊某個對象時,將展開該對象內部含有哪些對象,同時C區域也會顯示哪些對象引用了該對象github
點擊查看
某對象引用樹對象,在這裏面能看出其沒誰引用了,好比在內存泄漏中,能夠看出來它被誰引用,好比上圖,引用樹的第一行,能夠看出來,該對象被Object[12]
對象引用,索引值爲1,那咱們展開後,能夠看到,該Object[12]
是一個ArrayList
web
在3.X版本,Android Studio
採用了新的分析工具,但其使用都是相似的
其啓動界面以下
分析界面以下
數組
下載:http://eclipse.org/mat/downloads.php
MAT工具全稱爲Memory Analyzer Tool
,一款詳細分析Java堆內存的工具,該工具很是強大,爲了使用該工具,咱們須要hprof
文件。可是該文件不能直接被MAT使用,須要進行一步轉化,可使用hprof-conv
命令來轉化,可是Android Studio
能夠直接轉化,轉化方法以下
選擇一個hprof
文件,點擊右鍵選擇Export to standard .hprof
選項
MAT工具所需的文件就生成了,下面咱們用MAT來打開該工具:安全
File -> Open File
選擇咱們剛纔生成的hprof
文件hprof
文件可能過大,會有更長的時間解析,解析後,展示在咱們的面前的界面以下Overview
視圖Heap dump
佔用了多大的內存,其中涉及的類有多少,對象有多少,類加載器,若是有沒有回收的對象,會有一個鏈接,能夠直接參看(圖中的Unreachable Objects Histogram
)。Heap dump
佔用了41M的內存,5400個類,96700個對象,6個類加載器。Biggest Objects by Retained Size
histogram視圖
Dominator tree視圖
Leaks suspects視圖
在Navigation History
中能夠選擇Histogram
,而後右鍵加入對比,實現多個histogram
數據的對比結果,從而分析內存泄漏的可能性網絡
實時查看App分配的內存大小和空閒內存大小
發現Memory Leaks
app
在2.x的Android Studio
中,
能夠直接在Android studio
工具欄中直接點擊小機器人啓動
還能夠在Android studio
的菜單欄中Tools
或者是在sdk的tools
工具下打開
在3.x的IDE中,默認已經找不到啓動圖標,但在tools
目錄下依舊能夠打開使用
Heap Viewer
面板以下
按上圖的標記順序按下,咱們就能看到內存的具體數據,右邊面板中數值會在每次GC時發生改變,包括App自動觸發或者你來手動觸發
總覽:
列名 | 意義 |
---|---|
Heap Size |
堆棧分配給App的內存大小 |
Allocated |
已分配使用的內存大小 |
Free |
空閒的內存大小 |
%Used |
Allocated/Heap Size ,使用率 |
Objects |
對象數量 |
詳情:
類型 | 意義 |
---|---|
free |
空閒的對象 |
data object |
數據對象,類類型對象,最主要的觀察對象 |
class object |
類類型的引用對象 |
1-byte array(byte[],boolean[]) |
一個字節的數組對象 |
2-byte array(short[],char[]) |
兩個字節的數組對象 |
4-byte array(long[],double[]) |
4個字節的數組對象 |
non-Java object |
非Java對象 |
下面是每個對象都有的列名含義
列名 | 意義 |
---|---|
Count |
數量 |
Total Size |
總共佔用的內存大小 |
Smallest |
將對象佔用內存的大小從小往大排,排在第一個的對象佔用內存大小 |
Largest |
將對象佔用內存的大小從小往大排,排在最後一個的對象佔用的內存大小 |
Median |
將對象佔用內存的大小從小往大排,拍在中間的對象佔用的內存大小 |
Average |
平均值 |
當咱們點擊某一行時,能夠看到以下的柱狀圖
橫座標是對象的內存大小,這些值隨着不一樣對象是不一樣的,縱座標是在某個內存大小上的對象的數量
使用:在須要檢測內存泄漏的用例執行事後,手動GC下,而後觀察data object
一欄的total size
(也能夠觀察Heap Size/Allocated
內存的狀況),看看內存是否是會回到一個穩定值,屢次操做後,只要內存是穩定在某個值,那麼說明沒有內存溢出的,若是發現內存在每次GC後,都在增加,不論是慢增加仍是快速增加,都說明有內存泄漏的可能性
追蹤內存分配信息。能夠很直觀地看到某個操做的內存是如何進行一步一步地分配的
Allocation Tracker(AS)
工具比Allocation Tracker(Eclipse)
工具強大的地方是更炫酷,更清晰,可是能作的事情都是同樣的
Allocation Tracker
啓動
在內存圖中點擊途中標紅的部分,啓動追蹤,再次點擊就是中止追蹤,隨後自動生成一個alloc結尾的文件,這個文件就記錄了此次追蹤到的全部數據,而後會在右上角打開一個數據面板
面板左上角是全部歷史數據文件列表,後面是詳細信息,好,如今咱們來看詳細介紹信息面板
下面咱們用字母來分段介紹
Group by Method
:用方法來分類咱們的內存分配Group by Allocator
:用內存分配器來分類咱們的內存分配Group by Method
來組織,咱們來看看詳細信息:Size
上點擊一下就會倒序,若是以Count
排序也是同樣,Size
就是內存大小,Count
就是分配了多少次內存,點擊一下線程就會查看每一個線程裏全部分配內存的方法,而且能夠一步一步迭代到最底部Group by Allocator
來查看內存分配的狀況時,詳細信息區域就會變成以下78-4-1=73
次內存Jump To Source
按鈕Jump To Source
按鈕纔是可用的,都是跳轉到類Size
Thread1
)以及具體信息(分配了8821
次,分配了564.18k
的內存),可是紅框標註的區域並不表明Thread1
,而是第一個同心圓中佔比最大的那個線程,因此咱們如今把鼠標放到第一個同心圓上,能夠看出來,咱們劃過同心圓的軌跡時能夠看到右邊的樹枝變化了好幾個值Count/Size
的大小決定的。柱狀圖的內容其實和輪胎圖沒什麼特別的地方能夠直接在手機端查看內存泄露的工具
實現原理:本質上仍是用命令控制生成hprof
文件分析檢查內存泄露
https://github.com/square/leakcanary
在主模塊app下的build.gradle
下添加以下依賴
debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3.1' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3.1'
添加Application
子類
首先建立一個ExampleApplication
,該類繼承於Application
,在該類的onCreate方法中添加以下代碼開啓LeakCanary
監控:
LeakCanary.install(this);
在AndroidManifest.xml
中的application
標籤中添加以下信息:
android:name=".ExampleApplication"
這個時候安裝應用到手機,會自動安裝一個Leaks應用,以下圖
創建一個ActivityManager類,單例模式,裏面有一個數組用來保存Activity:
package com.example.android.sunshine.app; import android.app.Activity; import android.util.SparseArray; import android.view.animation.AccelerateInterpolator; import java.util.List; public class ActivityManager { private SparseArray<Activity> container = new SparseArray<Activity>(); private int key = 0; private static ActivityManager mInstance; private ActivityManager(){} public static ActivityManager getInstance(){ if(mInstance == null){ mInstance = new ActivityManager(); } return mInstance; } public void addActivity(Activity activity){ container.put(key++,activity); } }
而後在DetailActivity中的onCreate方法中將當前activity添加到ActivityManager的數組中:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); ActivityManager.getInstance().addActivity(this); if (savedInstanceState == null) { // Create the detail fragment and add it to the activity // using a fragment transaction. Bundle arguments = new Bundle(); arguments.putParcelable(DetailFragment.DETAIL_URI, getIntent().getData()); DetailFragment fragment = new DetailFragment(); fragment.setArguments(arguments); getSupportFragmentManager().beginTransaction() .add(R.id.weather_detail_container, fragment) .commit(); } }
咱們從首頁跳轉到詳情頁的時候會進入DetailActivity
的onCreate
的方法,而後就將當前activity
添加到了數組中,當返回時,咱們沒把他從數組中刪除。再次進入的時候,會建立新的activity
並添加到數組中,可是以前的activity
仍然被引用,沒法釋放,可是這個activity
不會再被使用,這個時候就形成了內存泄漏。咱們來看看LeakCanary
是如何報出這個問題的
解析的過程有點耗時,因此須要等待一會纔會在Leaks應用中,當咱們點開某一個信息時,會看到詳細的泄漏信息
從代碼層面分析性能問題,針對每一個方法來分析,好比當咱們發現咱們的應用出現卡頓的時候,咱們能夠來分析出現卡頓時在方法的調用上有沒有很耗時的操做,關注如下兩個問題:
打開Monitor,點擊圖中的標註的按鈕,啓動追蹤
打開App操做你的應用後,再次點擊的話就中止追蹤而且自動打開traceview分析面板
traceview
的面板分上下兩個部分:
左邊是線程信息,main線程就是Android應用的主線程,這個線程是都會有的,其餘的線程可能因操做不一樣而發生改變.每一個線程的右邊對應的是該線程中每一個方法的執行信息,左邊爲第一個方法執行開始,最右邊爲最後一個方法執行結束,其中的每個小立柱就表明一次方法的調用,你能夠把鼠標放到立柱上,就會顯示該方法調用的詳細信息
你能夠隨意滑動你的鼠標,滑倒哪裏,左上角就會顯示該方法調用的信息。
1.若是你想在分析面板中詳細查看該方法,能夠雙擊該立柱,分析面板自動跳轉到該方法
2.放大某個區域
剛打開的面板中,是咱們採集信息的總覽,可是一些局部的細節咱們看不太清,不要緊,該工具支持咱們放大某個特殊的時間段
若是想回到最初的狀態,雙擊時間線就能夠
3.每個方法的表示
能夠看出來,每個方法都是用一個凹型結構來表示,座標的凸起部分表示方法的開始,右邊的凸起部分表示方法的結束,中間的直線表示方法的持續
面板列名含義以下
名稱 | 意義 |
---|---|
Name |
方法的詳細信息,包括包名和參數信息 |
Incl Cpu Time |
Cpu執行該方法該方法及其子方法所花費的時間 |
Incl Cpu Time % |
Cpu執行該方法該方法及其子方法所花費佔Cpu總執行時間的百分比 |
Excl Cpu Time |
Cpu執行該方法所話費的時間 |
Excl Cpu Time % |
Cpu執行該方法所話費的時間佔Cpu總時間的百分比 |
Incl Real Time |
該方法及其子方法執行所話費的實際時間,從執行該方法到結束一共花了多少時間 |
Incl Real Time % |
上述時間佔總的運行時間的百分比 |
Excl Real Time % |
該方法自身的實際容許時間 |
Excl Real Time |
上述時間佔總的容許時間的百分比 |
Calls+Recur |
調用次數+遞歸次數,只在方法中顯示,在子展開後的父類和子類方法這一欄被下面的數據代替 |
Calls/Total |
調用次數和總次數的佔比 |
Cpu Time/Call |
Cpu執行時間和調用次數的百分比,表明該函數消耗cpu的平均時間 |
Real Time/Call |
實際時間於調用次數的百分比,該表該函數平均執行時間 |
你能夠點擊某個函數展開更詳細的信息
展開後,大多數有如下兩個類別:
Parents
:調用該方法的父類方法Children
:該方法調用的子類方法若是該方法含有遞歸調用,可能還會多出兩個類別:
Parents while recursive
:遞歸調用時所涉及的父類方法Children while recursive
:遞歸調用時所涉及的子類方法首先咱們來看當前方法的信息
列 | 值 |
---|---|
Name |
24 android/widget/FrameLayout.draw(L android/graphics/Canvas;)V |
Incl Cpu% |
20.9% |
Incl Cpu Time |
375.201 |
Excl Cpu Time % |
0.0% |
Excl Cpu Time |
0.000 |
Incl Real Time % |
1.1% |
Incl Real Time |
580.668 |
Excl Real Time % |
0.0% |
Excl Real Time |
0.000 |
Calls+Recur |
177+354 |
Cpu Time/Call |
0.707 |
Real Time/Call |
1.094 |
根據下圖中的toplevel能夠看出總的cpu執行時間爲1797.167ms
,當前方法佔用cpu的時間爲375.201
,375.201/1797.167=0.2087
,和咱們的Incl Cpu Time%
是吻合的。當前方法消耗的時間爲580.668
,而toplevel
的時間爲53844.141ms
,580.668/53844.141=1.07%
,和Incl Real Time %
也是吻合的。在來看調用次數爲177
,遞歸次數爲354
,和爲177+354=531
,375.201/531 = 0.7065
和Cpu Time/Call
也是吻合的,580.668/531=1.0935
,和Real Time/Call
一欄也是吻合的
Parents
Parents
一欄列 | 值 |
---|---|
Name |
22 com/android/internal/policy/impl/PhoneWindow$DecorView.draw(Landroid/graphics/Canvas;)V |
Incl Cpu% |
100% |
Incl Cpu Time |
375.201 |
Excl Cpu Time % |
無 |
Excl Cpu Time |
無 |
Incl Real Time % |
100% |
Incl Real Time |
580.668 |
Excl Real Time % |
無 |
Excl Real Time |
無 |
Call/Total |
177/531 |
Cpu Time/Call |
無 |
Real Time/Call |
無 |
其中的Incl Cpu Time%
變成了100%
,由於在這個地方,總時間爲當前方法的執行時間,這個時候的Incl Cpu Time%
只是計算該方法調用的總時間中被各父類方法調用的時間佔比,好比Parents
有2個父類方法,那就能看出每一個父類方法調用該方法的時間分佈。由於咱們父類只有一個,因此確定是100%
,Incl Real Time
一欄也是同樣的,重點是Call/Total
,以前咱們看當前方式時,這一欄的列名爲Call+Recur
,而如今變成了Call/Total
,這個裏面的數值變成了177/531
,由於總次數爲531
次,父類調用了177
次,其餘531
次是遞歸調用。這一數據能獲得的信息是,當前方法被調用了多少次,其中有多少次是父類方法調用的
Children
能夠看出來,咱們的子類有2個,一個是自身,一個是23android/view/View.draw(L android/graphics/Canvas;)V
,self
表明自身方法中的語句執行狀況,由上面能夠看出來,該方法沒有多餘語句,直接調用了其子類方法。另一個子類方法,能夠看出被當前方法調用了177次,可是該方法被其餘方法調用過,由於他的總調用次數爲892次,你能夠點擊進入子類方法的詳細信息中
Parents while recursive
列舉了遞歸調用當前方法的父類方法,以及其遞歸次數的佔比,猶豫咱們當前的方法遞歸了354
次,以上三個父類方法遞歸的次數分別爲348+4+2=354
次
Children while recursive
列舉了當遞歸調用時調用的子類方法
檢測資源文件是否有沒有用到的資源。
檢測常見內存泄露
安全問題
SDK版本安全問題
是否有費的代碼沒有用到
代碼的規範—甚至駝峯命名法也會檢測
自動生成的羅列出來
沒用的導包
可能的bug
Analyze -> Inspect Code
即可執行檢查
能夠檢查project,Module和指定文件
詳細信息