Memory Profiler 是 Android Studio自帶的內存分析工具,能夠幫助開發者很好的檢測內存的使用,在出現問題時,也能比較方便的分析定位問題,不過在使用的時候,好像並不是像本身一開始設想的樣子。java
若是想要看一個APP總體內存的使用,看APP heap就能夠了,不過須要注意Shallow Size跟Retained Size是意義,另外native消耗的內存是不會被算到Java堆中去的。數組
舉個例子,建立一個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類對象的大小大的多,以下圖:工具
能夠看到就總量而言Shallow Size基本能真是反應Java堆內存,而Retained Size卻明顯要高出很多, 由於Retained Size統計總內存的時候,基本不能避免重複統計的問題,好比:A對象有B對象的引用在計算總的對象大小的時候,通常會多出一個B,就像上圖,有個3個約40M的int[]對象,佔內存約120M,而每一個ListItem40MClass對象至少會再統計一次40M,這裏說的是至少,由於對象間可能還有其餘關係。咱們看下單個類的內存佔用-Instance Viewthis
能夠看到Head自己的Retained Size是120M ,Head->next 是80M,最後一個ListItem40MClass對象是40M,由於每一個對象的Retained Size除了包括本身的大小,還包括引用對象的大小,整個類的Retained Size大小累加起來就大了不少,因此若是想要看總體內存佔用,看Shallow Size仍是相對準確的,Retained Size能夠用來大概反應哪一種類佔的內存比較多,僅僅是個示意,不過仍是Retained Size比較經常使用,由於Shallow Size的大戶通常都是String,數組,基本類型意義不大,以下。spa
以前說Retained Size是此實例支配的內存大小,其實在Retained Size的統計上有不少限制,好比Depth:從任意 GC 根到所選實例的最短hop數,一個對象的Retained Size只會統計Depth比本身大的引用,而不會統計小的,這個多是爲了不重複統計而引入的,可是其實Retained Size在總體上是免不了重複統計的問題,因此纔會右下圖的狀況:線程
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
所以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卻會指數上升。
同以前40M的對比下,明顯上一個內存佔用更多,可是其實FinalizerReference的retain size卻更小。再來理解FinalizerReference跟內存泄漏的關係就比價好理解了,回收線程沒執行,實現了finalize方法的對象一直沒有被釋放,或者很遲才被釋放,這個時候其實就算是泄漏了。
好比下圖:Android 6.0 nexus5
從總體概況上看,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風險。
而且在Android 8.0或更高版本中,能夠更清楚的查看對象及內存的動態分配,並且不用dump內存,直接選中某一段,就能夠看這個時間段的內存分配:以下
如上圖,在時間點1 ,咱們建立了一個對象new ListItem40MClass(),ListItem40MClass有一個比較佔內存的byte數組,上面折線升高處有新對象建立,而後會發現內存大戶是byte數組,而最新的byte數組是在ListItem40MClass對象建立的時候分配的,這樣就能比較方便的看到,究竟是哪些對象致使的內存上升。