一 :通过 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中的数值是从上边的第一个表格的数值计算而来,那第一个表格的数值又是从何而来呢? 我们接着往下看!!
二 查看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相关的数据需要从显存中读取,此处暂时不做进一步的探讨。
可以清晰的看到,使用smaps统计出来的内存和使用adb shell dumpsys meminfo是一致的,但是smaps聚合统计到的数据,可以清晰的看到哪一个so、ttf、oat所占的内存,这部分信息adb shell dumpsys meminfo是不具有的。
从合并以后的结果来看,我们知道了进程每个部分详细的内存占用情况:
如果是Native部分内存占用的比较高,如果是Android 8.0以上,我们首先去分析Bimap占用的内存是否异常。如果Bitmap占用正常, 那么此部分就主要是通过malloc和mmap进行分配的,我们可以通过Loliprofile或者Malloc Debug进行进一步的堆栈抓取,来解决不合理的内存分配。也可以通过xHook或者字节跳动的Native Hook工具去hook内存分配函数做进一步的内存定位;
如果Code部分占用过多, 我们可以考虑优化包大小或者不合理的字体载入等;
如果Java Heap占用比较多,如果是Android 8.0 以下的设备,可以去看一下Bitmap的占用,如果Bitmap占用是正常的,需要分析是否有不合理的Java引用或者内存泄漏,此部分可以借助Android Studio自带的内存工具或者MAT做进一步分析, 此部分相对比较简单。
三 smaps文件
Linux在2.6版本之后有一个proc伪文件,在它下面记录各种信息,其中在proc/pid/smaps记录某个pid的内,smaps记录内存详细的使用情况。使用下面的命令可以读文件的值
cat /proc/pid/smaps
文件格式如下:
文件字段意义:
- 400ca000-400cb000:本段虚拟内存的地址范围
- r-xp :文件权限,r(读)、w(写)、x(执行)、p表示私有,s代表共享,如果不具有哪项权限用"-"代替
- 00000000 :映射文件的偏移量
- b3:11 :文件设备号
- 1345 :被映射到虚拟内存文件的映索节点
- /system/lib/libplddbgutil.so:文件名称
- Size:相应虚拟地址空间的大小
- RSS: 正在使用的物理内存的大小
- Shared_Clean: Rss中和其他进程共享的未使用页数
- Shared_Dirty: Rss和其他进程共享已经使用的页数
- Private_Clean: Rss私有区域未使用的页数
- Private_Dirty: Rss私有区域已经使用的页数
smaps文件一般有啥作用呢,譬如我们通过dumpsys meminfo 获取内存时,发现某一项内存数据异常,想弄清楚数据都是有哪些文件产生,我们就可以通过读取smaps详细排查。
原文
https://juejin.cn/post/7098341701999132686
https://juejin.cn/post/6844903540192706573