Android Memory(二) -- 应用内存占用分析

前言

在工作这几年,我一直深受内存问题的困扰,在和内存的不断抗争中,我逐渐积累了一些内存的知识,接来下来我会用几篇文章简单记录一下这几年的我学到的内存相关的经验。另外,本系列文章不去过多的分析Linux底层代码,只是探讨遇到内存问题时的解决方法论。 以下是全部文章的标题:

  1. Android Memory(一) – 内存基础知识
  2. Android Memory(二) – 应用内存占用分析
  3. Android Memory(三) – 问题定位与分析
  4. Android Memory(四) – 问题解决方案1
  5. Android Memory(五) – 问题解决方案2
  6. Android Memory(六) – 内存日常监控
应用内存占用分析

1. 通过Android Studio自带的Memory Profiler查看内存

日常开发中,我们在监控应用内存占用是,经常使用的是AS自带的Memory Profiler, 如下图所示:

  1. 强制执行垃圾回收
  2. 堆转储,把内存信息通过文件的方式保存下来,可以进行分析
  3. 记录内存分配情况, 此按钮仅在连接至运行 Android 7.1 或更低版本的设备时才会显示
  4. 放大/缩小时间线
  5. 跳转至实时内存数据
  6. Event 时间线,显示 Activity 状态、用户输入 Event 和屏幕旋转 Event\
  7. 内存使用量时间线,其包含以下内容:
  • 一个显示每个内存类别使用多少内存的堆叠图表,如左侧的 y 轴以及顶部的彩色键所示

  • 虚线表示分配的对象数,如右侧的 y 轴所示

  • 用于表示每个垃圾回收 Event 的图标

    我们可以明显的看到,在上图有当前应用每一部分所占用的内存,其中包括Other、Code、Stack、Graphics等部分的内存占用情况,那么这几部分的内存占用是怎么得到的呢,我们接下来会做进一步的分析。

2. 通过 adb shell dumpsys meminfo ${PROCESS_NAME} 查看内存情况

这里我们选取手机上的一个应用Phoneix查看一下其内存占用情况: 执行命令:adb shell dumpsys meminfo com.trassion.phoneix 显示结果如下:

在上图中,我们可以看到App Summary部分,就是通过Memory Profiler界面上查看到的数值。 那么,问题来了,App Summary部分又是怎么计算得来的呢?

在上图中,我们注意到了App Summary上边的第一个表格的数据,莫非是根据这一部分计算来的吗? 答案是Yes,下面我们给出详细的计算规则:

Java Heap = Dalvik Heap private dirty+ .art mmap private (clean+ dirty) = 27412 + 4 + 7824 = 35240

Native Heap = Native private dirty = 98156

Code = .so mmap private (clean + dirty) + .jar mmap private (clean + dirty) + .apk mmap private (clean + dirty) + .ttf mmap private (clean+ dirty) + .dex mmap private (clean + dirty) + .oat mmap private (clean + dirty) = (520 + 2348) + (8 + 500) + (60 + 1764) + (0 + 48) + (15808 + 2316) + (0 + 0) = 23440

Stack = Stack private dirty = 6144 

Graphics = GL mtrack private (clean + dirty) + EGL mtrack private(clean + dirty) = (0 + 0) + (28769 + 0) = 28769

Private other= TOTAL private (clean + dirty) - Java Heap - Native Heap- Code- Stack -Graphic = (205245 + 7780) - 35240 - 98156 - 23440 - 6144 - 28769 = 21276

System = TOTAL - TOTAL private (clean + dirty)  = 309121 - (205245 + 7780) = 96096 
  • Private Dirty

    进程本身使用的内存总数,包含了进程主动申请的以及修改的继承自Zygote的内存。其实Private Dirty表示了该进程私有的,不跟Disk数据一致的内存段。例如堆(heap),栈(stack),bss段。

注:在新平台上,用于管理Dalvik的内存(如, just-in-time compilation (JIT) and GC bookkeeping)不再像以前一样归到 Dalvik Heap,而是归类到 Dalvik Other。

  • Private clean

    进程独自使用的so和dex。Clean内存的好处是在内存紧张时,可以释放物理内存。因为是clean的,所以不需要写回到disk,只需要下次读取该内存(导致缺页错误)时再从disk读入。

  • Heap Size/ Heap Alloc/ Heap Free

    Heap Alloc是(Dalvik、native)app申请的内存记录,包括了Private Dirty和继承自Zygote的(多进程共享的)内存。所以,它是比Pss Total和Private Dirty都要大的。

我们知道了Memory Profiler中的数值是从上边的第一个表格的数值计算而来,那第一个表格的数值又是从何而来呢? 我们接着往下看!!

3. 通过adb shell run-as com.transsion.phoenix "cat /proc/${PID}/smaps 查看Smap数据

这里我们选取手机上的一个应用Phoneix查看一下其内存占用情况: 我这边实现了一个简单的脚本如下:

#! /bin/bash
process_name=$1
PID=$(adb shell pidof ${process_name})
adb shell run-as com.transsion.phoenix "cat /proc/${PID}/smaps > /data/local/tmp/smaps.txt"
adb pull /data/local/tmp/smaps.txt $2 

执行 bash pull_smaps.sh com.transsion.phoneix 1.0.0_smaps.txt

我们可以简单看一下相关的smap文件:

看起来是不是一头雾水,这一个个的内存区间都是干什么用的?

我从github上copy来一个python3的脚本,专门做smap数据的解析,目前作者已经找不到的!

解析结果如下(解析结果较长,我将部分解析结果做了省略):

Unknown : 9.861 M
	pss: 8.443 M
	swapPss: 1.418 M
		[anon:partition_alloc] : 8520 kB
		[anon:.bss] : 935 kB
		[anon:linker_alloc] : 189 kB
		[anon:bionic_alloc_small_objects] : 120 kB
		[anon:thread signal stack] : 24 kB
		[anon:cfi shadow] : 24 kB
		[anon:System property context nodes] : 20 kB
		[anon:atexit handlers] : 9 kB
		[anon:arc4random data] : 8 kB
		[anon:bionic_alloc_lob] : 4 kB
Dalvik : 30.602 M
	pss: 29.222 M
	swapPss: 1.380 M
		[anon:dalvik-main space (region space)] : 23336 kB
		[anon:dalvik-free list large object space] : 4233 kB
		[anon:dalvik-zygote space] : 2525 kB
		[anon:dalvik-non moving space] : 508 kB
Native : 126.734 M
	pss: 99.627 M
	swapPss: 27.107 M
		[anon:scudo:primary] : 84810 kB
		[anon:scudo:secondary] : 41924 kB
Dalvik Other : 21.381 M
	pss: 18.341 M
	swapPss: 3.040 M
		[anon:dalvik-LinearAlloc] : 10612 kB
		/memfd:jit-cache (deleted) : 6344 kB
		[anon:dalvik-DEX data] : 3364 kB
                ...
                ...
Stack : 8.188 M
	pss: 6.148 M
	swapPss: 2.040 M
		[anon:stack_and_tls:4854] : 168 kB
		[anon:stack_and_tls:4870] : 148 kB
		[stack] : 148 kB
		[anon:stack_and_tls:4878] : 144 kB
		[anon:stack_and_tls:4877] : 144 kB
		...
		...
Cursor : 0.000 M
	pss: 0.000 M
	swapPss: 0.000 M
Ashmem : 0.204 M
	pss: 0.204 M
	swapPss: 0.000 M
		/dev/ashmem/gralloc_shared_memory (deleted) : 50 kB
		/dev/ashmem/MessageQueue (deleted) : 48 kB
		/dev/ashmem/AshmemAllocator_hidl (deleted) : 38 kB
		...
		...
Gfx dev : 0.000 M
	pss: 0.000 M
	swapPss: 0.000 M
Other dev : 0.020 M
	pss: 0.020 M
	swapPss: 0.000 M
		/dev/binderfs/binder : 12 kB
		/dev/binderfs/hwbinder : 8 kB
.so mmap : 9.051 M
	pss: 8.839 M
	swapPss: 0.212 M
		/vendor/lib64/egl/libGLES_mali.so : 4044 kB
		/system/lib64/libhwui.so : 661 kB
		/system/lib64/libstagefright.so : 328 kB
		...
		...
.jar mmap : 2.217 M
	pss: 2.217 M
	swapPss: 0.000 M
		/system/framework/framework.jar : 1241 kB
		/data/data/com.transsion.phoenix/app_pccache/5/3ADD07A77E5BC23D41D5235C3F0C964B75D847A9/pcam.jar : 360 kB
		/apex/com.android.art/javalib/core-icu4j.jar : 265 kB
		/apex/com.android.art/javalib/core-oj.jar : 173 kB
		/apex/com.android.art/javalib/bouncycastle.jar : 78 kB
		...
		...
.apk mmap : 17.179 M
	pss: 16.923 M
	swapPss: 0.256 M
		/data/app/~~l7NnUJ5BUuoASfLO4ps6rQ==/com.google.android.webview-cmrXpqowpCjrrfYc0lesEQ==/base.apk : 14642 kB
		/data/user_de/0/com.google.android.gms/app_chimera/m/0000004d/dl-AdsFdrDynamite.integ_221310604100000.apk : 850 kB
		/data/app/~~WkMxdLO_EFZvRB7Y7O4Tfw==/com.transsion.phoenix-PVevdVSKlTJCD3Q2aDbscQ==/base.apk : 783 kB
		...
		...
.ttf mmap : 0.140 M
	pss: 0.140 M
	swapPss: 0.000 M
		/system/fonts/Roboto-Bold.ttf : 99 kB
		/system/fonts/Roboto-Regular.ttf : 37 kB
		/system/fonts/NotoSansLaoUI-Regular.ttf : 4 kB
.dex mmap : 44.585 M
	pss: 19.645 M
	swapPss: 24.940 M
		[anon:dalvik-classes7.dex extracted in memory from /data/app/~~WkMxdLO_EFZvRB7Y7O4Tfw==/com.transsion.phoenix-PVevdVSKlTJCD3Q2aDbscQ==/base.apk!classes7.dex] : 10932 kB
		[anon:dalvik-classes6.dex extracted in memory from /data/app/~~WkMxdLO_EFZvRB7Y7O4Tfw==/com.transsion.phoenix-PVevdVSKlTJCD3Q2aDbscQ==/base.apk!classes6.dex] : 10864 kB
		[anon:dalvik-classes.dex extracted in memory from /data/app/~~WkMxdLO_EFZvRB7Y7O4Tfw==/com.transsion.phoenix-PVevdVSKlTJCD3Q2aDbscQ==/base.apk] : 9328 kB
		...
		...
.oat mmap : 0.079 M
	pss: 0.079 M
	swapPss: 0.000 M
		/system/framework/arm64/boot-framework.oat : 36 kB
		/apex/com.android.art/javalib/arm64/boot.oat : 19 kB
		/apex/com.android.art/javalib/arm64/boot-core-icu4j.oat : 13 kB
		...
		...
.art mmap : 11.323 M
	pss: 8.984 M
	swapPss: 2.339 M
		[anon:dalvik-/system/framework/boot-framework.art] : 7112 kB
		[anon:dalvik-/apex/com.android.art/javalib/boot.art] : 1711 kB
		[anon:dalvik-/system/framework/boot-telephony-common.art] : 756 kB
		...
		...
Other mmap : 2.297 M
	pss: 2.297 M
	swapPss: 0.000 M
		/system/fonts/NotoSansCJK-Regular.ttc : 1030 kB
		/data/misc/shared_relro/libwebviewchromium64.relro : 626 kB
		/data/data/com.transsion.phoenix/app_webview/BrowserMetrics/BrowserMetrics-6281F38F-12BF.pma : 256 kB
		...
		... 

以上合并的计算结果就是第二部分中第一个表的数据,其中有略微的差异, 是因为两个dump的时机是有微小的时间间隔导致。另外此部分的合并数据,没有包含GL相关的数据,GL相关的数据需要从显存中读取,此处暂时不做进一步的探讨。

从合并以后的结果来看,我们知道了进程每个部分详细的内存占用情况:

  • 如果是Native部分内存占用的比较高,如果是Android 8.0以上,我们首先去分析Bimap占用的内存是否异常。如果Bitmap占用正常, 那么此部分就主要是通过malloc和mmap进行分配的,我们可以通过Loliprofile或者Malloc Debug进行进一步的堆栈抓取,来解决不合理的内存分配。也可以通过xHook或者字节跳动的Native Hook工具去hook内存分配函数做进一步的内存定位;

  • 如果Code部分占用过多, 我们可以考虑优化包大小或者不合理的字体载入等;

  • 如果Java Heap占用比较多,如果是Android 5.0以上,Android 8.0 以下的设备,可以去看一下Bitmap的占用,如果Bitmap占用是正常的,需要分析是否有不合理的Java引用或者内存泄漏,此部分可以借助Android Studio自带的内存工具或者MAT做进一步分析, 此部分相对比较简单。

步的内存定位;

  • 如果Code部分占用过多, 我们可以考虑优化包大小或者不合理的字体载入等;

  • 如果Java Heap占用比较多,如果是Android 5.0以上,Android 8.0 以下的设备,可以去看一下Bitmap的占用,如果Bitmap占用是正常的,需要分析是否有不合理的Java引用或者内存泄漏,此部分可以借助Android Studio自带的内存工具或者MAT做进一步分析, 此部分相对比较简单。

最后

如果大伙有什么好的学习方法或建议欢迎大家在评论中积极留言哈,希望大家能够共同学习、共同努力、共同进步。

小编在这里祝小伙伴们在未来的日子里都可以 升职加薪,当上总经理,出任CEO,迎娶白富美,走上人生巅峰!!

不论遇到什么困难,都不应该成为我们放弃的理由!

很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些问题,比如学了一段时间感觉没有方向感,不知道该从那里入手去学习,需要一份小编整理出来的学习资料的关注我主页或者点击扫描下方二维码免费领取~

这里是关于我自己的Android 学习,面试文档,视频收集大整理,有兴趣的伙伴们可以看看~

如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言,一定会认真查询,修正不足,谢谢。

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Android内存分析是指通过分析应用程序在运行过程中的内存使用情况,找出内存泄漏、内存溢出等问题,以优化应用程序的性能和稳定性。常用的工具有Android Studio提供的内存分析器和MAT(Memory Analyzer Tool)等。 在进行Android内存分析时,可以采取以下步骤: 1. 监测内存使用:使用Android Studio提供的内存监视工具,观察应用程序在运行过程中的内存使用情况,包括堆内存和非堆内存的使用情况。 2. 寻找内存泄漏:通过观察内存使用情况,查找是否有对象没有被正确释放,从而导致内存泄漏。可以使用内存分析器来分析堆快照,查找对象引用关系,找出不再需要的对象。 3. 优化内存占用:观察哪些对象占用了大量内存,并尝试优化其内存占用。例如,可以考虑使用弱引用或软引用来管理对象,减少不必要的缓存等。 4. 避免内存溢出:注意合理管理大数据集合、避免频繁创建大对象、及时释放不需要的资源等,以避免应用程序因为内存溢出而崩溃。 5. 使用内存分析工具:Android Studio提供了内存分析器,可以帮助开发者分析内存使用情况,找出内存泄漏和优化内存占用。MAT是一款Java内存分析工具,也可用于Android内存分析。 通过进行Android内存分析,开发者可以及时发现和解决应用程序的内存问题,提升应用程序的性能和用户体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值