Memory Profiler 是 Android Studio自帶的內(nèi)存分析工具,可以幫助開發(fā)者很好的檢測內(nèi)存的使用,在出現(xiàn)問題時(shí),也能比較方便的分析定位問題,不過在使用的時(shí)候,好像并非像自己一開始設(shè)想的樣子。
如何查看整體的內(nèi)存使用概況
如果想要看一個(gè)APP整體內(nèi)存的使用,看APP heap就可以了,不過需要注意Shallow Size跟Retained Size是意義,另外native消耗的內(nèi)存是不會(huì)被算到Java堆中去的。

- Allocations:堆中的實(shí)例數(shù)。
- Shallow Size:此堆中所有實(shí)例的總大?。ㄒ宰止?jié)為單位)。其實(shí)算是比較真實(shí)的java堆內(nèi)存
- Retained Size:為此類的所有實(shí)例而保留的內(nèi)存總大小(以字節(jié)為單位)。這個(gè)解釋并不準(zhǔn)確,因?yàn)镽etained Size會(huì)有大量的重復(fù)統(tǒng)計(jì)
- native size:8.0之后的手機(jī)會(huì)顯示,主要反應(yīng)Bitmap所使用的像素內(nèi)存(8.0之后,轉(zhuǎn)移到了native)
舉個(gè)例子,創(chuàng)建一個(gè)List的場景,有一個(gè)ListItem40MClass類,自身占用40M內(nèi)存,每個(gè)對(duì)象有個(gè)指向下一個(gè)ListItem40MClass對(duì)象的引用,從而構(gòu)成List,
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();
}
}
我們創(chuàng)建三個(gè)這樣的對(duì)象,并形成List,示意如下
A1->next=A2
A2->next=A3
A3->next= null
這個(gè)時(shí)候用Android Profiler查看內(nèi)存,會(huì)看到如下效果:Retained Size統(tǒng)計(jì)要比實(shí)際3個(gè)ListItem40MClass類對(duì)象的大小大的多,如下圖:

可以看到就總量而言Shallow Size基本能真是反應(yīng)Java堆內(nèi)存,而Retained Size卻明顯要高出不少, 因?yàn)镽etained Size統(tǒng)計(jì)總內(nèi)存的時(shí)候,基本不能避免重復(fù)統(tǒng)計(jì)的問題,比如:A對(duì)象有B對(duì)象的引用在計(jì)算總的對(duì)象大小的時(shí)候,一般會(huì)多出一個(gè)B,就像上圖,有個(gè)3個(gè)約40M的int[]對(duì)象,占內(nèi)存約120M,而每個(gè)ListItem40MClass對(duì)象至少會(huì)再統(tǒng)計(jì)一次40M,這里說的是至少,因?yàn)閷?duì)象間可能還有其他關(guān)系。我們看下單個(gè)類的內(nèi)存占用-Instance View
- Depth:從任意 GC 根到所選實(shí)例的最短 hop 數(shù)。
- Shallow Size:此實(shí)例的大小。
- Retained Size:此實(shí)例支配的內(nèi)存大?。ǜ鶕?jù) dominator 樹)。
可以看到Head本身的Retained Size是120M ,Head->next 是80M,最后一個(gè)ListItem40MClass對(duì)象是40M,因?yàn)槊總€(gè)對(duì)象的Retained Size除了包括自己的大小,還包括引用對(duì)象的大小,整個(gè)類的Retained Size大小累加起來就大了很多,所以如果想要看整體內(nèi)存占用,看Shallow Size還是相對(duì)準(zhǔn)確的,Retained Size可以用來大概反應(yīng)哪種類占的內(nèi)存比較多,僅僅是個(gè)示意,不過還是Retained Size比較常用,因?yàn)镾hallow Size的大戶一般都是String,數(shù)組,基本類型意義不大,如下。

FinalizerReference大小跟內(nèi)存使用及內(nèi)存泄漏的關(guān)系
之前說Retained Size是此實(shí)例支配的內(nèi)存大小,其實(shí)在Retained Size的統(tǒng)計(jì)上有很多限制,比如Depth:從任意 GC 根到所選實(shí)例的最短hop數(shù),一個(gè)對(duì)象的Retained Size只會(huì)統(tǒng)計(jì)Depth比自己大的引用,而不會(huì)統(tǒng)計(jì)小的,這個(gè)可能是為了避免重復(fù)統(tǒng)計(jì)而引入的,但是其實(shí)Retained Size在整體上是免不了重復(fù)統(tǒng)計(jì)的問題,所以才會(huì)右下圖的情況:

FinalizerReference中refrent的對(duì)象的retain size是40M,但是沒有被計(jì)算到FinalizerReference的retain size中去,而且就圖表而言FinalizerReference的意義其實(shí)不大,F(xiàn)inalizerReference對(duì)象本身占用的內(nèi)存不大,其次FinalizerReference的retain size統(tǒng)計(jì)的可以說是FinalizerReference的重復(fù)累加的和,并不代表其引用對(duì)象的大小,僅僅是ReferenceQueue<Object> queue中ReferenceQueue的累加,
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;
}
}
}
...
}
每個(gè)FinalizerReference retained size 都是其next+ FinalizerReference的shallowsize,反應(yīng)的并不是其refrent對(duì)象內(nèi)存的大小,如下:

因此FinalizerReference越大只能說明需要執(zhí)行finalize的對(duì)象越多,并且對(duì)象是通過強(qiáng)引用被持有,等待Deamon線程回收??梢酝ㄟ^該下代碼試驗(yàn)下:
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();
}
}
}
多次點(diǎn)擊后,可以看到finalize的對(duì)象線性上升,而FinalizerReference的retain size卻會(huì)指數(shù)上升。

同之前40M的對(duì)比下,明顯上一個(gè)內(nèi)存占用更多,但是其實(shí)FinalizerReference的retain size卻更小。再來理解FinalizerReference跟內(nèi)存泄漏的關(guān)系就比價(jià)好理解了,回收線程沒執(zhí)行,實(shí)現(xiàn)了finalize方法的對(duì)象一直沒有被釋放,或者很遲才被釋放,這個(gè)時(shí)候其實(shí)就算是泄漏了。
如何看Profiler的Memory圖
- 第一:看整體Java內(nèi)存使用看shallowsize就可以了
- 第二:想要看哪些對(duì)象占用內(nèi)存較多,可以看Retained Size,不過看Retained Size的時(shí)候,要注意過濾一些無用的比如 FinalizerReference,基本類型如:數(shù)組對(duì)象
比如下圖:Android 6.0 nexus5

從整體概況上看,Java堆內(nèi)存的消耗是91兆左右,而整體的shallow size大概80M,其余應(yīng)該是一些堆?;A(chǔ)類型的消耗,而在Java堆棧中,占比最大的是byte[],其次是Bitmap,bitmap中的byte[]也被算進(jìn)了前面的byte[] retain size中,而FinilizerReference的retain size已經(jīng)大的不像話,沒什么參考價(jià)值,可以看到Bitmap本身其實(shí)占用內(nèi)存很少,主要是里面的byte[],當(dāng)然這個(gè)是Android8.0之前的bitmap,8.0之后,bitmap的內(nèi)存分配被轉(zhuǎn)移到了native。
再來對(duì)比下Android8.0的nexus6p:可以看到占大頭的Bitmap的內(nèi)存轉(zhuǎn)移到native中去了,降低了OOM風(fēng)險(xiǎn)。

并且在Android 8.0或更高版本中,可以更清楚的查看對(duì)象及內(nèi)存的動(dòng)態(tài)分配,而且不用dump內(nèi)存,直接選中某一段,就可以看這個(gè)時(shí)間段的內(nèi)存分配:如下

如上圖,在時(shí)間點(diǎn)1 ,我們創(chuàng)建了一個(gè)對(duì)象new ListItem40MClass(),ListItem40MClass有一個(gè)比較占內(nèi)存的byte數(shù)組,上面折線升高處有新對(duì)象創(chuàng)建,然后會(huì)發(fā)現(xiàn)內(nèi)存大戶是byte數(shù)組,而最新的byte數(shù)組是在ListItem40MClass對(duì)象創(chuàng)建的時(shí)候分配的,這樣就能比較方便的看到,到底是哪些對(duì)象導(dǎo)致的內(nèi)存上升。
總結(jié)
- 總體Java內(nèi)存使用看shallow size
- retained size只是個(gè)參考,不準(zhǔn)確,存在各種重復(fù)統(tǒng)計(jì)問題
- FinalizerReference retained size 大小極其不準(zhǔn)確,而且其強(qiáng)引用的對(duì)象并沒有被算進(jìn)去,不過finilize確實(shí)可能導(dǎo)致內(nèi)存泄漏
- native size再8.0之后,對(duì)Bitmap的觀測有幫助。