當 GC 工做時,虛擬機中止其餘工做。頻繁地觸發 GC 進行內存回收,會致使系統性能嚴重降低。php
在極短的時間內,分配大量的內存,而後又釋放它,這種現象就會形成內存抖動。典型地,在 View 控件的 onDraw 方法裏分配大量內存,又釋放大量內存,這種作法極易引發內存抖動,從而致使性能降低。由於 onDraw 裏的大量內存分配和釋放會給系統堆空間形成壓力,觸發 GC 工做去釋放更多可用內存,而 GC 工做起來時,又會吃掉寶貴的幀時間 (幀時間是 16ms) ,最終致使性能問題。html
Java 語言的內存泄漏概念和 C/C++ 不太同樣,在 Java 裏是指不正確地引用致使某個對象沒法被 GC 釋放,從而致使可用內存愈來愈少。好比,一個圖片查看程序,使用一個靜態 Map 實例來緩存解碼出來的 Bitmap 實例來加快加載進度。這個時候就可能存在內存泄漏。java
內存泄漏會致使可用內存愈來愈少,從而致使頻繁觸發 GC 回收內存,進而致使性能降低。android
經過一個很是簡單的例子來演示內存抖動。這個例子裏,在自定義 View 的 onDraw 方法裏大量分配內存來演示內存抖動和性能之間的關係。canvas
版本一:緩存
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); String msg = ""; for (int i = 0; i < 500; i++) { if (i != 0) { msg += ", "; } msg += Integer.toString(i + 1); } Log.d("DEBUG", msg); }
版本二:性能優化
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); StringBuilder sb = new StringBuilder(); for (int i = 0; i < 500; i ++) { if (i != 0) { sb.append(", "); } sb.append(i + 1); } Log.d("DEBUG", sb.toString()); }
內存抖動的特徵:從 Memory Monitor 來看,有毛刺出現。即短期內分配大量的內存並觸發 GC。oracle
從 Allocation Tracker 裏看,一次操做會有大量的內存分配產生。app
這個例子裏,咱們簡單地讓點擊 Settings 菜單,就產生一個 100KB 的內存泄漏。dom
private void addSomeCache() { // add 100KB cache int key = new Random().nextInt(100); Log.d("sfox", "add cache for key " + key); sCache.put(key, new byte[102400]); }
內存泄漏的特徵:從 Memory Monitor 來看,內存佔用愈來愈大
利用 MAT 工具進行專業分析。這是個很大的話題。幾乎能夠獨立成幾個章節來說。能夠參閱 MAT 自己自帶的 Tutorials 來學習。另外,這篇文章裏的分析方法是個不錯的開始。
一個典型的問題是 Android 系統越用越慢。這種典型地是由內存泄漏引發的。一個頗有用的解決這種問題的辦法是:比較先後兩個階段的內存的使用狀況。通常流程以下:
好比針對撥號盤愈來愈慢的問題,咱們能夠開機後啓動撥號盤,打進打出10個電話。而後抓個 HPROF 文件。接着,再打進打出10個電話,再抓一個 HPROF 文件。接着拿這兩個文件對比分析,看是否是會形成電話打進打出越多,內存佔用越多的狀況發生。
HPROF文件:HPROF 簡單地理解,就是從 jvm 裏 dump 出來的內存和 CPU 使用狀況的一個二進制文件。它的英文全名叫 A Heap/CPU Profiling Tool。這裏有它完整的官方文檔和它的歷史介紹。
打開 MAT 後,會有一個 Tutorials 來教你們怎麼用。這裏列出幾個操做步驟及其注意事項。
在咱們的示例程序裏面,每次點擊 Settings 菜單,都會致使一次100KB的內存泄漏。下面是咱們利用上面介紹的流程來查找內存泄漏問題。咱們先點擊 5 次 Settings 菜單,而後手動觸發一次 GC,再導出 HPROF 文件。接着,咱們再點擊 6 次 Settings 菜單,而後手動觸發一次 GC,再導出第二份 HPROF 文件。咱們拿這兩份 HPROF 就能夠作一些對比。
經過上圖能夠看到,兩次操做確實致使了某些類的實例增長了。圖中能夠清楚地看到 byte[] 和 java.util.HashMap$HashMapEntry 兩個類增長得比較明顯。這樣,咱們隨便選擇一個,經過 OQL 來查詢系統中的這個內存。
從上圖能夠找到,本次 dump 出來的內存裏,確實有不少個這個類的實例。在圖上右擊任何一個實例,右擊,選擇 Paths to GC roots ,能夠找到這個實例是被誰引用的。
從上圖能夠看出來,這個內存是被 MainActivity 裏的 sCache 引用的。經過閱讀代碼,咱們就能夠找到這個漏洞了。即每次都往 sCache 裏保存一個引用。