
文集目錄
ps:喜歡的點贊哦 android性能跟蹤分析工具系列 - 目錄
好了上次說了 LeakCanary 檢測內(nèi)存泄露,我在最后說了下可以自己抓內(nèi)存快照來分析內(nèi)存泄露的,比 LeakCanary 快速多了。好了,不賣關(guān)子了,這個就是 studio 自帶的工具 Memory monitor,我們來看一下:


這個就是studio 內(nèi)置的內(nèi)存監(jiān)控工具,其實下面還有 CPU/GPU/NetWork 監(jiān)控工具,這里不說這幾個,直說內(nèi)存監(jiān)控工具,他主要給我們提供了2個分析功能,分別是我標記的1和2:
- 1 : jump java heap ,抓取堆內(nèi)存快照,也是本篇介紹的部分
- 2: allocation tracker ,分析內(nèi)存分配,是一個過程分析工具,從時間A到時間 B 的內(nèi)存分配,這部分內(nèi)容之后再說。
然后我們再看圖中的圖標,這個很簡單,但是也很有用的,深藍色的 app 已經(jīng)使用的內(nèi)存值,淺藍色的已經(jīng)分配給 JVM但是還未使用的內(nèi)存值,根據(jù)觀察,空閑內(nèi)存不足20%時必定會觸發(fā)一次 GC,而且大家記不記得4大組件都有一個低內(nèi)存回調(diào)函數(shù):
@Override
public void onLowMemory() {
super.onLowMemory();
}
這個函數(shù)我看很多人說都是在空閑內(nèi)存不足20%時觸發(fā)的。
好了我們來正式看下本篇的主角 jump java heap
如何使用 jump java heap
要是用 jump java heap 功能很簡單,點擊上面1的位置的按鈕就行,注意啊,這個是抓取堆內(nèi)存的快照,所以這是分析一個時間節(jié)點,而不是時間段哦,不要混了。

第一次看數(shù)據(jù)量有些大,不要頭大,這個還是很好看的:
- 1 : 即使左邊的寫著 name 的列表,里面有 bety[]/ArrayList[]/String[] 等,這很好理解,就是把當(dāng)前堆內(nèi)存中的對象按照分類給我們列出來,比如 ArrayList[] 就是當(dāng)前內(nèi)存中的集合對象啦,bety[] 就是字節(jié)數(shù)組啊,結(jié)合android,大家想內(nèi)存中誰會大量使用bety[]字節(jié)數(shù)組啊,當(dāng)然是 bitmap 位圖啦,當(dāng)然我們自定義的對象也是可以看到的,只不過默認的分類排序是按照占用內(nèi)存大小來排列的。
- 2: 我們點擊1中的每個分類,在2中就會把該分類的對象列出來
- 3: 引用指向這塊內(nèi)查的對象有哪些
- 4: 這是內(nèi)存泄露的分析工具,一會說
好了,整個工具我們粗粗的看了下, 先有個簡單的認知,大家仔細看可以看到每一塊都會顯示很多參數(shù)數(shù)據(jù)對吧,這個其實才是重中之重
數(shù)據(jù)參數(shù)詳解:
- total :是一共有多少個對象
- heap count:是一共分配了多少快內(nèi)存空間,我看這個參數(shù)沒啥用。
- size of:是平均一個對象的大小
- Shallow Size: 是這些對象一共占用了多少內(nèi)存
- Retained Sizes/Domintaing Sizes: 這些對象釋放以后可以獲得多少內(nèi)存,這個值最有用,和實際的內(nèi)存變化值對的上。
- depth : 對象引用層級,從 GC root 根節(jié)點開始算。
我們點擊每一個參數(shù)都是可以排序的哦
結(jié)合實際例子學(xué)分析
我準備了一個小測試,可以學(xué)習(xí)如何查看我們想要找的對象,也可以查找 activity 和 非 activity類型對象的內(nèi)存泄露,這倆其實是一回事,因為都是可以在這張對內(nèi)存快照中看出來的
例子: A 頁面點 按鈕啟動 B 頁面,在B 頁面創(chuàng)建幾個數(shù)據(jù),一個 Book 對象,一個 Book 對象的集合,然后把這個 Book 對象和 Book 對象的集合放到 application 的靜態(tài)對象中,模擬內(nèi)存泄露
public class BActivity extends AppCompatActivity {
private ArrayList<Book> mList;
private Book mBook;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);
mBook = new Book("HHH");
mList = new ArrayList<>(10000);
MyApplication.getInstance().mActivity = this;
MyApplication.getInstance().mList = mList;
MyApplication.getInstance().mBook = mBook;
}
代碼,大家看一下,很簡單,我們這么玩,A 啟動 B 后,我們看下此時 B 的堆內(nèi)存快照,然后我們關(guān)閉 B頁面再來看一下堆內(nèi)存快照
A 啟動 B :

首先,我們點擊左上角的選擇款,選擇按包分類,選擇上圖藍色的標簽

按包排序后,我們再來找我們自定義的對象就方便啦,我們先看 book 這個對象,book 對象的 total 是1,說明此時堆內(nèi)存只有1個 book 對象,然后右邊的 instance 也是只有一個,說明book 對象的確是只有一個,然后我們看下面有2個 mBook對象,這說明有2個對象的引用指向堆內(nèi)存中的這個 book 對象,工具很細心的給我們列出了引用關(guān)系,一個在 application,一個在 B 頁面。

我們再來看下 BActivity 這個對象,右面的 instance點擊是可以展開的,展開后我們可以看到 B 頁面對象內(nèi)的所有參數(shù),上圖我們按實際內(nèi)存消耗大小排序的,可以看到我們聲明的那個10000個容量的mlist集合對象占用40K 內(nèi)存,消耗很大的,所以我們平時寫 java 代碼有其是 new 對象時一定要謹慎,內(nèi)存就是這么消耗沒的,哈哈至于 mbook 對象,因為內(nèi)存消耗小自然在下面,大家翻翻都能找到。
恩,這樣我們就可以看到一個頁面的總消耗和哪里消耗最大了,這樣我們是可以找到優(yōu)化點的
關(guān)閉 B 返回 A:

關(guān)閉 B 頁面后,我們看下此時app內(nèi)存占用,還是5.8M,和 B 顯示時一樣,這說明啥,肯定是內(nèi)存泄露了唄,B 對象沒被回收啊

我們再來看下內(nèi)存快照,這時 BActiviy的 total 還是1,說明 b 頁面對象還在,這就是妥妥的泄露了,從這2點都能看出來,尤其是你看下面,藍色的那一條,出現(xiàn)藍色就說明這里泄露了,引用關(guān)系指向 application,這下大家會找了吧,這個不難吧。

我們再來看 book 對象,book 對象里也有一條藍色,說明 book 對象也泄露了,當(dāng)然下面的那個引用藏在 B 頁面對象里了,B 對象收回了,這個 book 對象也跟著回收了,不是直接泄露,這個工具是檢測不出來的。
肯定有人就問了,appcalition 里不是還有 book 的集合不是也泄露了嘛,是的,集合類型不是我們自定義的,是在 java 包下面的,我們需要在 java 包下面去找。
這里有一個小技巧要說,很重要,不注意可能找不到結(jié)果的,在右邊對象列表中,尤其是像系統(tǒng)集合這類的,里面的對象很多,默認不會都顯示出來,我們需要拉倒最底下,點擊展開全部,這點很重要,不要漏掉


然后我們按照 實際消耗內(nèi)存排序,可以看到第一個就是我們要找的,看下面也是大藍條,一樣是內(nèi)存泄露的,引用也是指向 application 的。
另外這個工具可以自動分析 activity 對象的內(nèi)存泄露,我們點擊右邊的 Analyzer tasks 展開,然后點擊左上角的作色箭頭就好了。

這個工具只能分析 activity 對象,要是能分析全部就好了。 哈哈,說到這里基本這個工具如何使用就介紹完了,對于查找內(nèi)存泄露是不是比 LeakCanary 快多了,就是比他麻煩些,需要我們自己去翻
不過呢,這個工具最麻煩的一點就在于需要我們在可能泄露的點不停的去點一下,看看沒有沒藍條。需要一些經(jīng)驗才能真正達到快速高效,所以說有一個好鼠標很重要哇,哈哈哈。
如何查看圖片的內(nèi)存消耗
寫完上面呢基本的這個內(nèi)存快照打擊就知道怎么看了,但是呢這里我還是要重點說說 bitmap 位圖的內(nèi)存消耗,一個 app 內(nèi)存消耗的主力可是bitmap,這塊我們必須要會看。
bitmap 位圖在內(nèi)存快照中快照中的位置我在上面說了,是 byte[] ,字節(jié)數(shù)組中
例子:還是結(jié)合一個例子來說,一個頁面,初始不加載圖片,有一個按鈕,點一下加載一張大圖,1080P的模擬一下,在點擊的同事回收上一次顯示的 bitmap 內(nèi)存,回收和回收我們都來看一下。
切換圖片
public void nextImage(View view) {
List<Integer> images = getImages();
if (index == images.size() - 1) {
index = 0;
}
recycleBitmap(image);
image.setImageResource(images.get(index));
index++;
}
回收 bitmap 資源
public void recycleBitmap(View view) {
Toast.makeText(this, "test", Toast.LENGTH_SHORT).show();
Drawable drawable = image.getDrawable();
if (drawable != null && drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
Bitmap bitmap = bitmapDrawable.getBitmap();
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
}
}
代碼很簡單,我們分階段看下,顯示1張圖,2張圖,3張圖,對比下
不回收 bitmap 資源
app初始:


顯示一張圖片:


顯示二張圖片:


顯示三張圖片:


具體我就不算了,大家可以看到隨著圖片的切換顯示,內(nèi)存使用量是在不停攀升的,內(nèi)存上漲的幅度和 byte[] 數(shù)組增長的幅度一致,我們直接看最后一張圖,這是顯示3張圖片之后,可以看到內(nèi)存占用前4個都是 bitmap 占用的,這時我們使用工具上的手動 GC 看看,能回收到少內(nèi)存。



手動GC之后,可以看到內(nèi)存下來了,byte[]組數(shù)中只有前2個是bitmap 占用的了,之前的2個打 bitmap 被回收了,這說明bitmap 回收異常重要,大家看看內(nèi)存消耗都能深有體會了吧。
那么我們來看看我們主動回收bitmap 資源會怎樣把,是不是和我們想的一樣
回收 bitmap 資源
這次簡單點,不上這么多圖了,代碼上添加了回收資源的方法 : recycleBitmap(image);
顯示1張圖:

顯示2張圖:

顯示3張圖:


可以看到隨著我們不停顯示圖片,app內(nèi)存消耗還是在不停上漲的,我在看下 byte[] 中強5個都是 bitmap 的占用,然后我們點擊第一個bitmap 對象

可以看到,bitmap.recycler 只是把bitmap對象的引用清理掉了,然后等著 GC 回收了,但是這幾次并沒有觸發(fā) GC,然后繼續(xù)切換圖片,顯示到第5張時觸發(fā)了 GC


可以看到 GC 的確回收了 沒有引用關(guān)系bitmap 的內(nèi)存,但是還有一個沒有引用關(guān)系的bitmap沒有被回收,看來我們把 bitmap.recycler 之后,馬上 GC 的話,這個 bitmap 會被忽略,至于為啥,我也不知道啊,也許是IPC 延遲的問題吧。
可以看到 GC 的對于 bitmap 的重要性了,既然系統(tǒng) GC 相應(yīng)的不及時,那么我們就自動手動調(diào)用 GC 好了,當(dāng)然 GC 不要很頻繁,因為 GC 操作是會卡 UI線程的,我覺得在 application 的全局生命周期監(jiān)聽函數(shù)中,在 activity 的銷毀那里調(diào)一下可能是個不錯的時機
另外 5.0 之后 使用 System.gc 不管用了,需要這樣才行 Runtime.getRuntime().gc();
this.registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() {
@Override
public void onActivityDestroyed(Activity activity) {
Runtime.getRuntime().gc();
}
ps:關(guān)于bitmap 這塊都是我不成熟的認識,有錯誤請留言,有好的方案也屏留言。
AS 3.0 性能分析工具的新變化
AS 升級 3.0 之后,性能分析工具變化較大,首先性能分析工具被分到了一個新的位置 Android Prefiler 中,這個功能窗口默認是加入底部的,需要自己開啟



新的 Android Prefiler 功能窗口主要變化在于5個部分:
- 設(shè)備
- 當(dāng)前選擇的應(yīng)用進程
- 時間線縮放操作
- 跳轉(zhuǎn)到詳情界面,點擊時間軸也可以
- 高級信息顯示
可以顯示頁面活動狀態(tài),用戶輸入事件和屏幕旋轉(zhuǎn)事件
默認是不顯示 5 這部分內(nèi)容的,因為會增加構(gòu)建,編譯時間,此時會在5的位置會提示
Advanced profiling is unavailable for the selected process
高級數(shù)據(jù)顯示包括以下內(nèi)容:
- 所有分析器窗口上的事件時間軸
- activity 頁面顯示和切換
- 內(nèi)存分析器中已分配對象的數(shù)量
- 內(nèi)存分析器中的垃圾收集事件
- 有關(guān)Network Profiler中所有傳輸文件的詳細信息
如何啟用高級數(shù)據(jù)顯示:
- 單擊自動提示的 Edit Configurations 或是 Run > Edit Configurations
- 在左窗格中選擇您的應(yīng)用程序模塊。
- 單擊Profiling選項卡,然后選中Enable advanced profiling.
- 必須重新構(gòu)建安裝才可以