Android Studio Profiler Memory (内存分析工具)的简单使用及问题分析

简介: Android Studio Profiler Memory (内存分析工具)的简单使用及问题分析

Memory Profiler 是 Android Studio自带的内存分析工具,可以帮助开发者很好的检测内存的使用,在出现问题时,也能比较方便的分析定位问题,不过在使用的时候,好像并非像自己一开始设想的样子。


如何查看整体的内存使用概况


如果想要看一个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,

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,示意如下


A1->next=A2
A2->next=A3 
A3->next= null

这个时候用Android Profiler查看内存,会看到如下效果:Retained Size统计要比实际3个ListItem40MClass类对象的大小大的多,如下图:


image.png可以看到就总量而言Shallow Size基本能真是反应Java堆内存,而Retained Size却明显要高出不少, 因为Retained Size统计总内存的时候,基本不能避免重复统计的问题,比如:A对象有B对象的引用在计算总的对象大小的时候,一般会多出一个B,就像上图,有个3个约40M的int[]对象,占内存约120M,而每个ListItem40MClass对象至少会再统计一次40M,这里说的是至少,因为对象间可能还有其他关系。我们看下单个类的内存占用-Instance View


  • 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,数组,基本类型意义不大,如下。

image.png


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的累加,

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对象内存的大小,如下:


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的观测有帮助。


目录
相关文章
|
3月前
|
开发框架 前端开发 Android开发
Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势
本文深入探讨了 Flutter 与原生模块(Android 和 iOS)之间的通信机制,包括方法调用、事件传递等,分析了通信的必要性、主要方式、数据传递、性能优化及错误处理,并通过实际案例展示了其应用效果,展望了未来的发展趋势。这对于实现高效的跨平台移动应用开发具有重要指导意义。
291 4
|
3月前
|
SQL 人工智能 Dart
Android Studio的插件生态非常丰富
Android Studio的插件生态非常丰富
170 1
|
3月前
|
Ubuntu Linux Android开发
Android Studio支持多种操作系统
Android Studio支持多种操作系统
145 1
|
4天前
|
前端开发 Java 编译器
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
65 36
当flutter react native 等混开框架-并且用vscode-idea等编译器无法打包apk,打包安卓不成功怎么办-直接用android studio如何打包安卓apk -重要-优雅草卓伊凡
|
3天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
21 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
3月前
|
安全 Android开发 数据安全/隐私保护
深入探索Android与iOS系统安全性的对比分析
在当今数字化时代,移动操作系统的安全已成为用户和开发者共同关注的重点。本文旨在通过比较Android与iOS两大主流操作系统在安全性方面的差异,揭示两者在设计理念、权限管理、应用审核机制等方面的不同之处。我们将探讨这些差异如何影响用户的安全体验以及可能带来的风险。
108 21
|
2月前
|
Java 开发工具 Android开发
安卓与iOS开发环境对比分析
在移动应用开发的广阔天地中,安卓和iOS两大平台各自占据半壁江山。本文深入探讨了这两个平台的开发环境,从编程语言、开发工具到用户界面设计等多个角度进行比较。通过实际案例分析和代码示例,我们旨在为开发者提供一个清晰的指南,帮助他们根据项目需求和个人偏好做出明智的选择。无论你是初涉移动开发领域的新手,还是寻求跨平台解决方案的资深开发者,这篇文章都将为你提供宝贵的信息和启示。
41 8
|
3月前
|
JavaScript
如何使用内存快照分析工具来分析Node.js应用的内存问题?
需要注意的是,不同的内存快照分析工具可能具有不同的功能和操作方式,在使用时需要根据具体工具的说明和特点进行灵活运用。
73 3
|
2月前
|
存储 缓存 数据安全/隐私保护
DMA(Direct Memory Access):直接内存访问
DMA(Direct Memory Access)是一种允许外设直接与内存进行数据传输的技术,无需 CPU 干预。它通过减轻 CPU 负担、提高数据传输效率来提升系统性能。DMA 的工作模式包括直接模式和 FIFO 模式,数据传输方式有单字传送和块传送,寻址模式有增量寻址和非增量寻址。通过缓存一致性协议、同步机制、数据校验和合理的内存管理,DMA 确保了数据在内存中的一致性和完整性。
123 0
|
6天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
60 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex

热门文章

最新文章