Android Studio Profiler Memory (內存分析工具)的簡單使用及問題

Memory Profiler 是 Android Studio自帶的內存分析工具,能夠幫助開發者很好的檢測內存的使用,在出現問題時,也能比較方便的分析定位問題,不過在使用的時候,好像並不是像本身一開始設想的樣子。java

如何查看總體的內存使用概況

若是想要看一個APP總體內存的使用,看APP heap就能夠了,不過須要注意Shallow Size跟Retained Size是意義,另外native消耗的內存是不會被算到Java堆中去的。數組

image.png

  • Allocations:堆中的實例數。
  • Shallow Size:此堆中全部實例的總大小(以字節爲單位)。其實算是比較真實的java堆內存
  • Retained Size:爲此類的全部實例而保留的內存總大小(以字節爲單位)。這個解釋並不許確,由於Retained Size會有大量的重複統計
  • native size:8.0以後的手機會顯示,主要反應Bitmap所使用的像素內存(8.0以後,轉移到了native)

舉個例子,建立一個List的場景,有一個ListItem40MClass類,自身佔用40M內存,每一個對象有個指向下一個ListItem40MClass對象的引用,從而構成List,dom

class ListItem40MClass {

    byte[] content = new byte[1000 * 1000 * 40];
    ListItem40MClass() {
        for (int i = 0; i < content.length; i++) {
            content[i] = 1;
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
    }

    ListItem40MClass next;
}


@OnClick(R.id.first)
void first() {
    if (head == null) {
        head = new ListItem40MClass();
    } else {
        ListItem40MClass tmp = head;
        while (tmp.next != null) {
            tmp = tmp.next;
        }
        tmp.next = new ListItem40MClass();
    }
}
複製代碼

咱們建立三個這樣的對象,並造成List,示意以下ide

A1->next=A2
A2->next=A3 
A3->next= null
複製代碼

這個時候用Android Profiler查看內存,會看到以下效果:Retained Size統計要比實際3個ListItem40MClass類對象的大小大的多,以下圖:工具

281540022720_.pic_hd.jpg

能夠看到就總量而言Shallow Size基本能真是反應Java堆內存,而Retained Size卻明顯要高出很多, 由於Retained Size統計總內存的時候,基本不能避免重複統計的問題,好比:A對象有B對象的引用在計算總的對象大小的時候,通常會多出一個B,就像上圖,有個3個約40M的int[]對象,佔內存約120M,而每一個ListItem40MClass對象至少會再統計一次40M,這裏說的是至少,由於對象間可能還有其餘關係。咱們看下單個類的內存佔用-Instance Viewthis

  • Depth:從任意 GC 根到所選實例的最短 hop 數。
  • Shallow Size:此實例的大小。
  • Retained Size:此實例支配的內存大小(根據 dominator 樹)。

能夠看到Head自己的Retained Size是120M ,Head->next 是80M,最後一個ListItem40MClass對象是40M,由於每一個對象的Retained Size除了包括本身的大小,還包括引用對象的大小,整個類的Retained Size大小累加起來就大了不少,因此若是想要看總體內存佔用,看Shallow Size仍是相對準確的,Retained Size能夠用來大概反應哪一種類佔的內存比較多,僅僅是個示意,不過仍是Retained Size比較經常使用,由於Shallow Size的大戶通常都是String,數組,基本類型意義不大,以下。spa

291540025853_.pic.jpg

FinalizerReference大小跟內存使用及內存泄漏的關係

以前說Retained Size是此實例支配的內存大小,其實在Retained Size的統計上有不少限制,好比Depth:從任意 GC 根到所選實例的最短hop數,一個對象的Retained Size只會統計Depth比本身大的引用,而不會統計小的,這個多是爲了不重複統計而引入的,可是其實Retained Size在總體上是免不了重複統計的問題,因此纔會右下圖的狀況:線程

image.png

FinalizerReference中refrent的對象的retain size是40M,可是沒有被計算到FinalizerReference的retain size中去,並且就圖表而言FinalizerReference的意義其實不大,FinalizerReference對象自己佔用的內存不大,其次FinalizerReference的retain size統計的能夠說是FinalizerReference的重複累加的和,並不表明其引用對象的大小,僅僅是ReferenceQueue queue中ReferenceQueue的累加,code

public final class FinalizerReference<T> extends Reference<T> {
    // This queue contains those objects eligible for finalization.
    public static final ReferenceQueue<Object> queue = new ReferenceQueue<Object>();

    // Guards the list (not the queue).
    private static final Object LIST_LOCK = new Object();

    // This list contains a FinalizerReference for every finalizable object in the heap.
    // Objects in this list may or may not be eligible for finalization yet.
    private static FinalizerReference<?> head = null;

    // The links used to construct the list.
    private FinalizerReference<?> prev;
    private FinalizerReference<?> next;

    // When the GC wants something finalized, it moves it from the 'referent' field to
    // the 'zombie' field instead.
    private T zombie;

    public FinalizerReference(T r, ReferenceQueue<? super T> q) {
        super(r, q);
    }

    @Override public T get() {
        return zombie;
    }

    @Override public void clear() {
        zombie = null;
    }

    public static void add(Object referent) {
        FinalizerReference<?> reference = new FinalizerReference<Object>(referent, queue);
        synchronized (LIST_LOCK) {
            reference.prev = null;
            reference.next = head;
            if (head != null) {
                head.prev = reference;
            }
            head = reference;
        }
    }

    public static void remove(FinalizerReference<?> reference) {
        synchronized (LIST_LOCK) {
            FinalizerReference<?> next = reference.next;
            FinalizerReference<?> prev = reference.prev;
            reference.next = null;
            reference.prev = null;
            if (prev != null) {
                prev.next = next;
            } else {
                head = next;
            }
            if (next != null) {
                next.prev = prev;
            }
        }
    }
...
}
複製代碼

每一個FinalizerReference retained size 都是其next+ FinalizerReference的shallowsize,反應的並非其refrent對象內存的大小,以下:cdn

image.png

所以FinalizerReference越大隻能說明須要執行finalize的對象越多,而且對象是經過強引用被持有,等待Deamon線程回收。能夠經過該下代碼試驗下:

class ListItem40MClass {
        byte[] content = new byte[5];

        ListItem40MClass() {
            for (int i = 0; i < content.length; i += 1000) {
                content[i] = 1;
            }
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            LogUtils.v("finalize ListItem40MClass");
        }

        ListItem40MClass next;
    }


    @OnClick(R.id.first)
    void first() {
        if (head == null) {
            head = new ListItem40MClass();
        } else {
            for (int i = 0; i < 1000; i++) {
                ListItem40MClass tmp = head;
                while (tmp.next != null) {
                    tmp = tmp.next;
                }
                tmp.next = new ListItem40MClass();
            }
        }
    }
複製代碼

屢次點擊後,能夠看到finalize的對象線性上升,而FinalizerReference的retain size卻會指數上升。

image.png

同以前40M的對比下,明顯上一個內存佔用更多,可是其實FinalizerReference的retain size卻更小。再來理解FinalizerReference跟內存泄漏的關係就比價好理解了,回收線程沒執行,實現了finalize方法的對象一直沒有被釋放,或者很遲才被釋放,這個時候其實就算是泄漏了。

如何看Profiler的Memory圖

  • 第一:看總體Java內存使用看shallowsize就能夠了
  • 第二:想要看哪些對象佔用內存較多,能夠看Retained Size,不過看Retained Size的時候,要注意過濾一些無用的好比 FinalizerReference,基本類型如:數組對象

好比下圖:Android 6.0 nexus5

image.png

從總體概況上看,Java堆內存的消耗是91兆左右,而總體的shallow size大概80M,其他應該是一些堆棧基礎類型的消耗,而在Java堆棧中,佔比最大的是byte[],其次是Bitmap,bitmap中的byte[]也被算進了前面的byte[] retain size中,而FinilizerReference的retain size已經大的不像話,沒什麼參考價值,能夠看到Bitmap自己其實佔用內存不多,主要是裏面的byte[],固然這個是Android8.0以前的bitmap,8.0以後,bitmap的內存分配被轉移到了native。

再來對比下Android8.0的nexus6p:能夠看到佔大頭的Bitmap的內存轉移到native中去了,下降了OOM風險。

image.png

而且在Android 8.0或更高版本中,能夠更清楚的查看對象及內存的動態分配,並且不用dump內存,直接選中某一段,就能夠看這個時間段的內存分配:以下

image.png

如上圖,在時間點1 ,咱們建立了一個對象new ListItem40MClass(),ListItem40MClass有一個比較佔內存的byte數組,上面折線升高處有新對象建立,而後會發現內存大戶是byte數組,而最新的byte數組是在ListItem40MClass對象建立的時候分配的,這樣就能比較方便的看到,究竟是哪些對象致使的內存上升。

總結

  • 整體Java內存使用看shallow size
  • retained size只是個參考,不許確,存在各類重複統計問題
  • FinalizerReference retained size 大小極其不許確,並且其強引用的對象並無被算進去,不過finilize確實可能致使內存泄漏
  • native size再8.0以後,對Bitmap的觀測有幫助。
相關文章
相關標籤/搜索