内存分析工具

1. 内存问题分析原则

a. 对比分析

​ 除非有明确的问题指向,内存问题很难明确使用量是否合理,所以一般和对比机(如升级前版本、竞品机)进行对比分析,找出使用量的差异点,然后针对差异进行分析。

b. 分别定位

​ 系统dumpsys 的meminfo信息有限,需要先meminfo中定位出内存使用异常的种类(heap/mmap/mtrack),然后对不同种类使用不同的分析方法。

2.内存获取与拆分:

​ 通畅我们获取的内存信息来自meminfo,但是这部分只能看到内存的大小,无法确定内存增多或者减少的原因。因此需要按照内存不同种类采用不同方法进行分析。

​ 通常将内存信息分为4个部分来看,分别是native heapdalvik heapmmapGL**/EGL mtrack**。这4个部分内存分别对应不同的分析工具,后面详细介绍。

2.1 Java heap

Java heap对应Dalvik Heap中的Private Dirty与art mmap中Private Dirty +Clean的和。Dalvik Heap使用对应工具分析,art mmap通过smaps分析。

  /**​
735           * Pss of Java Heap bytes in KB due to the application.​
736           * Notes:​
737           *  * OTHER_ART is the boot image. Anything private here is blamed on​
738           *    the application, not the system.​
739           *  * dalvikPrivateDirty includes private zygote, which means the​
740           *    application dirtied something allocated by the zygote. We blame​
741           *    the application for that memory, not the system.​
742           *  * Does not include OTHER_DALVIK_OTHER, which is considered VM​
743           *    Overhead and lumped into Private Other.​
744           *  * We don't include dalvikPrivateClean, because there should be no​
745           *    such thing as private clean for the Java Heap.​
746           * @hide​
747           */
748          @UnsupportedAppUsage
749          public int getSummaryJavaHeap() {​
750              return dalvikPrivateDirty + getOtherPrivate(OTHER_ART);​
751          }​

/************************************************************************************/
574                  case OTHER_ART: return ".art mmap";

2.2 Native heap

Native heap对应Native heap中的Private Dirty(不包含SwapPss Dirty),异常时拆解到Native heap内存分析。

753          /**​
754           * Pss of Native Heap bytes in KB due to the application.​
755           * Notes:​
756           *  * Includes private dirty malloc space.​
757           *  * We don't include nativePrivateClean, because there should be no​
758           *    such thing as private clean for the Native Heap.​
759           * @hide​
760           */
761          @UnsupportedAppUsage
762          public int getSummaryNativeHeap() {​
763              return nativePrivateDirty;​
764          }​

2.3 Code

code对应mmap各个值的总和(不包含other mmap),code异常时拆解到mmap内存分析。

766          /**​
767           * Pss of code and other static resource bytes in KB due to​
768           * the application.​
769           * @hide​
770           */
771          @UnsupportedAppUsage
772          public int getSummaryCode() {​
773              return getOtherPrivate(OTHER_SO)​
774                + getOtherPrivate(OTHER_JAR)​
775                + getOtherPrivate(OTHER_APK)​
776                + getOtherPrivate(OTHER_TTF)​
777                + getOtherPrivate(OTHER_DEX)​
778                  + getOtherPrivate(OTHER_OAT)​
779                  + getOtherPrivate(OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE)​
780                  + getOtherPrivate(OTHER_DALVIK_OTHER_APP_CODE_CACHE);​
781          }​


/************************************************************************************/
568                  case OTHER_SO: return ".so mmap";​
569                  case OTHER_JAR: return ".jar mmap";​
570                  case OTHER_APK: return ".apk mmap";​
571                  case OTHER_TTF: return ".ttf mmap";​
572                  case OTHER_DEX: return ".dex mmap";​
573                  case OTHER_OAT: return ".oat mmap";​
585                  case OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE: return ".ZygoteJIT";​
586                  case OTHER_DALVIK_OTHER_APP_CODE_CACHE: return ".AppJIT";

2.4 Stack

Stack对应Stack中Pss Total.

783          /**​
784           * Pss in KB of the stack due to the application.​
785           * Notes:​
786           *  * Includes private dirty stack, which includes both Java and Native​
787           *    stack.​
788           *  * Does not include private clean stack, because there should be no​
789           *    such thing as private clean for the stack.​
790           * @hide​
791           */
792          @UnsupportedAppUsage
793          public int getSummaryStack() {​
794              return getOtherPrivateDirty(OTHER_STACK);​
795          }​

/************************************************************************************/
563                  case OTHER_STACK: return "Stack";

2.5 Graphics

Graphics对应EGL GL Gfx(MTK机型不含Gfx)三个值的总和,Graphics异常时拆解到GL/EGL/Gfx内存分析。

797          /**​
798           * Pss in KB of graphics due to the application.​
799           * Notes:​
800           *  * Includes private Gfx, EGL, and GL.​
801           *  * Warning: These numbers can be misreported by the graphics drivers.​
802           *  * We don't include shared graphics. It may make sense to, because​
803           *    shared graphics are likely buffers due to the application​
804           *    anyway, but it's simpler to implement to just group all shared​
805           *    memory into the System category.​
806           * @hide​
807           */
808          @UnsupportedAppUsage
809          public int getSummaryGraphics() {​
810              return getOtherPrivate(OTHER_GL_DEV)​
811                + getOtherPrivate(OTHER_GRAPHICS)​
812                + getOtherPrivate(OTHER_GL);​
813          }​

/************************************************************************************/
566                  case OTHER_GL_DEV: return "Gfx dev";​
576                  case OTHER_GRAPHICS: return "EGL mtrack";​
577                  case OTHER_GL: return "GL mtrack"

2.6 System

其为 共享内存 + SwapPss。SwapPss只是系统将暂时未用到的内存回收至swap区,依然需要从heap、mmap、EGL/GL mtrack入手进行拆解。

共享内存是对某块内存进行共享使用,现未发现有较大占用的情况,若需要计算每块共享内存,可使用每一项的Pss Total - Private Dirty - Private Clean

     /**​
832           * Pss in KB due to the system.​
833           * Notes:​
834           *  * Includes all shared memory.​
835           * @hide​
836           */
837          @UnsupportedAppUsage
838          public int getSummarySystem() {​
839              return getTotalPss()​
840                - getTotalPrivateClean()​
841                - getTotalPrivateDirty();​
842          }

3. Native heap 分析工具:

3.1 perfetto(通用工具)

https://perfetto.dev/docs/case-studies/memory#heapprofd

Analyzing the Native Heap:

注意使用 debug包,例如分析system_server进程:

$ ./heap_profile -n system_server​

Profiling active. Press Ctrl+C to terminate.​
You may disconnect your device.​

Wrote profiles to /tmp/profile-1283e247-2170-4f92-8181-683763e17445 (symlink /tmp/heap_profile-latest)​
These can be viewed using pprof. Googlers: head to pprof/ and upload them.

heap_profile

heap_profile_csdn(推荐使用这个)

https://blog.csdn.net/cfc1243570631/article/details/139231897

3.2 Native Heap Insight(推荐使用小米工具)

pre版-debug包

堆内存检视方案(Native Heap Insight)

3.3 hprof文件(通用工具)

https://blog.csdn.net/archie_7/article/details/114292255

3.3.1 获取及使用方法:

adb shell am dumpheap 包名​
adb pull /data/local/tmp/某.hprof​
//使用下列命令进行文件转化​
hprof-conv a.hprof b.hprof​
拖到Android studio 即可

或者在这里打开hprof文件

1、Allocations代表这个class存在几个对象

2、Native Size代表Native内存占用

3、Shallow Size 是指用于存储其本身的内存大小

4、Retained Size 该类关联的所有实例占用内存大小

点击Leaks,Profile会列出当前内存信息中检测到的内存泄露点,如下列出了9点:

3.3.2 官网介绍:

https://developer.android.com/studio/profile/memory-profiler?hl=zh-cn

  1. 选择要查看的heap的类型:all/app/image/zygote/JNI heaps
  • default heap:当系统未指定堆时。

  • image heap:系统启动映像,包含启动期间预加载的类。此处的分配确保绝不会移动或消失。

  • zygote heap:写时复制堆,其中的应用进程是从 Android 系统中派生的。

  • app heap:您的应用在其中分配内存的主堆。

  • JNI heap:显示 Java 原生接口 (JNI) 引用被分配和释放到什么位置的堆。

  1. 选择要查看的范围:class、package、callstack
  • Arrange by class:根据类名称对所有分配进行分组。这是默认值。

  • Arrange by package:根据软件包名称对所有分配进行分组。

  • Arrange by callstack:将所有分配分组到其对应的调用堆栈。

  1. Allocations:堆中的分配数。

  2. Native Size:此对象类型使用的原生内存总量(以字节为单位)。只有在使用 Android 7.0 及更高版本时,才会看到此列。您会在此处看到采用 Java 分配的某些对象的内存,因为 Android 对某些框架类(如 Bitmap)使用原生内存。

  3. Shallow Size:此对象类型使用的 Java 内存总量(以字节为单位)。

  4. Retained Size:为此类的所有实例而保留的内存总大小(以字节为单位)。

3.4 Malloc debug

malloc_debug

Dalvik heap分析工具

perfetto

https://perfetto.dev/docs/case-studies/memory#java-hprof

Analyzing the Java Heap:

使用方法: ./脚本 -n 进程名

$ ./java_heap_dump -n com.android.systemui​

Dumping Java Heap.​
Wrote profile to /tmp/tmpup3QrQprofile​
This can be viewed using https://ui.perfetto.dev.

java_heap_dump

3.5 mmap分析工具:

smaps获取与解析

使用步骤:

3.5.1. 使用如下命令获取进程PID:

如分析电话进程:

mi@odd:~$ adb shell ps -A |grep com.android.phone
# 输出
radio         2310   843 16412940 124840 do_epoll_wait      0 S com.android.phone

3.5.2. 使用如下命令获取进程smaps信息:

如分析电话进程:

adb pull /proc/2310/smaps

**注意:**需要抓取多份,以防止内存波动带来的干扰。每次抓取后及时修改文件名称,防止后面数据覆盖前面的数据。

3.5.3. 解析脚本2(推荐):

结合dumpsys meminfo,如果发现3中脚本统计结果和meminfo对不上,可以试下如下脚本:

python3 smaps_parser -f smaps > *.txt

脚本下载 http://minio.898311.xyz:8900/blogfile/17298583128804.smaps_parser

3.6 EGL/GL分析工具:

GL、EGL内存

MTK机型EGL/GL内存异常情况梳理

3.7 内存优化方向:

https://zhuanlan.zhihu.com/p/607182345

参考资料:

https://juejin.cn/post/7096059314233671694