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对象建立的时候分配的,这样就能比较方便的看到,究竟是哪些对象致使的内存上升。