1. 线下内存测试
经过上一篇的学习,我们了解了内存的线上监控方式,通过监控数据,我们可以及时感知到线上的内存使用情况。除了线上监控,我们也有必要经常在线下进行内存测试,本节我们来了解线下内存测试方法。 线下内存测试的主要目的如下。
- 获取 App 整体和各个场景的内存指标,包括虚拟内存、物理内存。
- 在内存异常时,明确具体是哪种内存异常,比如Java内存、Native内存或者哪个动态库异常。
- 初步分析导致问题的原因。
1.1 获取 App 的内存指标
做内存测试,首先需要知道当前App的整体内存情况,在发生异常后再递进到具体的内存指标。在Android中,我们可以通过以下这些方式获取整体内存指标。
- 获取虚拟内存总大小及swap值:/proc/${pid}/status。
- 获取进程的各类型内存使用量:dumpsys meminfo--local $ {pid}。
- 获取较为详细的内存数据:/proc/ <math xmlns="http://www.w3.org/1998/Math/MathML"> p i d / m a p s , 首先我们可以通过 a d b s h e l l c a t / p r o c / {pid}/maps, 首先我们可以通过adb shell cat /proc/ </math>pid/maps,首先我们可以通过adbshellcat/proc/{pid}/status 获取进程总的虚拟内存大小,如下代码所示。
yaml
blueline:/proc/2718/task $ cat /proc/2718/status
Name: ndroid.settings
Umask: 0077
State: S (sleeping)
Tgid: 2718
Ngid: 0
Pid: 2718
PPid: 1141
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 512
Groups: 1000 1007 1065 1077 1079 3001 3002 3003 3007 9997
VmPeak: 15693312 kB
VmSize: 15601468 kB
VmLck: 0 kB
VmPin: 0 kB
VmHWM: 258484 kB
VmRSS: 231956 kB
RssAnon: 136140 kB
RssFile: 94264 kB
RssShmem: 1552 kB
VmData: 1071572 kB
VmStk: 8192 kB
VmExe: 12 kB
VmLib: 150156 kB
VmPTE: 1636 kB
VmPMD: 76 kB
VmSwap: 0 kB
Threads: 52
SigQ: 8/13062
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000080001204
SigIgn: 0000000000000001
SigCgt: 0000006e400084f8
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000
Seccomp: 2
Speculation_Store_Bypass: unknown
Cpus_allowed: ff
Cpus_allowed_list: 0-7
Mems_allowed: 1
Mems_allowed_list: 0
voluntary_ctxt_switches: 49548
nonvoluntary_ctxt_switches: 4640
可以看到,通过/proc/${pid}/status,我们可以获取到很多有用的信息,如下所示。
- FDSize:文件描述符数量,部分设备在其文件描述符数量超出上限后会崩溃。
- VmPeak:虚拟内存的峰值。
- VmSize:当前虚拟内存大小。
- VmSwap:交换虚拟内存大小。
- Threads:线程数。
通过这些数据,我们可以初步获取到内存指标,接下来获取物理内存相关的数据。
我们可以通过adb shell dumpsys meminfo--local获取到当前App的整体物理内存和详细分类的物理内存的数据
yaml
blueline:/proc/2718/task $ dumpsys meminfo --local 2718
Applications Memory Usage (in Kilobytes):
Uptime: 67064914 Realtime: 142831264
** MEMINFO in pid 2718 [com.android.settings] **
Pss Private Private Swap Rss Heap Heap Heap
Total Dirty Clean Dirty Total Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------
Native Heap 59320 59260 0 0 63352 0 0 0
Dalvik Heap 22980 22848 0 0 30280 0 0 0
Dalvik Other 4008 3792 0 0 4872
Stack 1004 1004 0 0 1008
Ashmem 15 0 0 196 736
Gfx dev 10832 10832 0 0 10832
Other dev 150 0 136 0 528
.so mmap 2790 204 60 0 26984
.jar mmap 2391 0 60 0 23232
.apk mmap 18119 0 15528 0 22704
.ttf mmap 67 0 4 0 300
.dex mmap 1544 12 1512 0 2312
.oat mmap 1014 0 12 0 9508
.art mmap 2403 1856 8 0 32248
Other mmap 1765 32 964 0 3968
EGL mtrack 27744 27744 0 0 27744
GL mtrack 384 384 0 0 384
Unknown 935 616 312 0 2092
TOTAL 157465 128584 18596 196 263084 0 0 0
App Summary
Pss(KB) Rss(KB)
------ ------
Java Heap: 24712 62528
Native Heap: 59260 63352
Code: 17408 85472
Stack: 1004 1008
Graphics: 38960 38960
Private Other: 5836
System: 10285
Unknown: 11764
TOTAL PSS: 157465 TOTAL RSS: 263084 TOTAL SWAP (KB): 196
可以看到dumpsys meninfo,可以获取当前App的整体物理内存情况和各个类型的物理内存信息
- Java Heap: Java物理内存
- Naitve Heap: Naitive物理内存
- Code: 文件映射的物理内存
- Graphics: Graphics物理内存,如图片、纹理内存
- TOTAL: 总的物理内存
- Java内存可以分为Dalvik Heap和Dalvik Other
- Graphics还可以分为Gfx dev、EGL mtrack、GL mtrack
1.2 获取进程的内存空间数据
/proc/ <math xmlns="http://www.w3.org/1998/Math/MathML"> p i d / m a p s 可以为我们提供某个进程的虚拟内存空间的详细数据 / p r o c / {pid}/maps可以为我们提供某个进程的虚拟内存空间的详细数据 /proc/ </math>pid/maps可以为我们提供某个进程的虚拟内存空间的详细数据/proc/{pid}/smaps得到进程的各个内存的整体虚拟内存大小和物理内存大小
1.3 分析内存使用情况
要分析Java内存问题,通过adb shell am dumpheap生成prof文件
bash
blueline:/ # am dumpheap com.android.settings
File: /data/local/tmp/heapdump-20250928-135701.prof
Waiting for dump to finish...
blueline:/ # exit
H:\mqtt\sbin>cd ../../
H:>adb pull /data/local/tmp/heapdump-20250928-135701.prof .
/data/local/tmp/heapdump-20250928-135701.prof: 1 file pulled, 0 skipped. 8.2 MB/s (56012887 bytes in 6.477s)
通过hprof-conf转换成.hprof文件,然后使用Android Studio Proflier打开分析
hprof-conv heapdump-20250928-135701.prof heapdump-20250928-135701.hprof