对拆解system中主要是对比测试机和对比机之间的差距,测试机那些地方高于对比机
拆解表,作为理解
在拆解表中system测试机比对比机多出113M
这说明是有问题的
对system拆解:
system | 1 | 2 | 3 | 4 | 5 |
对比机 | 91022 | 94380 | 94106 | 93910 | 81628 |
测试机 | 102520 | 103318 | 103183 | 103160 | 103458 |
差值 | 11498 | 8938 | 9077 | 9250 | 21830 |
Java Heap | 1 | 2 | 3 | 4 | 5 |
对比机 | 132084 | 153256 | 172620 | 106680 | 92732 |
测试机 | 154816 | 182344 | 121392 | 147832 | 98896 |
差值 | 22732 | 29088 | -51228 | 41152 | 6164 |
Java Heap | 1 | 2 | 3 | 4 | 5 |
对比机 | 58136 | 55360 | 55692 | 55348 | 54488 |
测试机 | 42568 | 42292 | 44340 | 44104 | 44464 |
差值 | -15568 | -13068 | -11352 | -11244 | -10024 |
Code | 1 | 2 | 3 | 4 | 5 |
对比机 | 63928 | 64872 | 64052 | 64392 | 62700 |
测试机 | 164736 | 165088 | 155324 | 155492 | 155540 |
差值 | 100808 | 100216 | 91272 | 91100 | 92840 |
Stack | 1 | 2 | 3 | 4 | 5 |
对比机 | 4348 | 4092 | 4092 | 4092 | 3984 |
测试机 | 3496 | 3500 | 3504 | 3508 | 3508 |
差值 | -852 | -592 | -588 | -584 | -476 |
Graphics | 1 | 2 | 3 | 4 | 5 |
对比机 | 16620 | 16620 | 16620 | 16620 | 16620 |
测试机 | 1992 | 1992 | 1992 | 1992 | 1992 |
差值 | -14628 | -14628 | -14628 | -14628 | -14628 |
Private Other | 1 | 2 | 3 | 4 | 5 |
对比机 | 13768 | 13604 | 13608 | 13624 | 13772 |
测试机 | 41928 | 41936 | 41952 | 41944 | 41616 |
差值 | 28160 | 28332 | 28344 | 28320 | 27844 |
上面拆解表是模块2的集合版本
从上面表我们可以看出来内存主要出现最大差异的是Code
我们可以根据此表进程计算拆解,模块2主要是模块1的总体进程
所以我们要知道模块2中的Code,那些在模块1中
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
这个公式只是计算出各个模块2中的各项指标,只是案例不要和自己项目中进行对比,但是公式是一样的,没有什么区别
详细教学:
https://www.jianshu.com/p/af22eb653fc3
内存拆解分析通用文档
内存分析通用文档
说明
该文档对常见的内存超标问题进行总结,并给出内存拆解方式
一. 初略分析
查看process整体内存状态的两种方式:
dumpsys meminfo <pid>
根据测试提供的dumpsys meminfo数据,可先初略看一下内存状态,如下图
tab1是较为详细的内存分布,一般详细的拆解需要从tab1中查看;
tab4为Objects是统计App内部组件对象个数,其中Views、ViewRootImpl以及Activities个数,若这些项占用过多,考虑出现了内存泄露或是测试出现异常;
tab2则是对tab1的再统计,其它字段较易理解,Graphics和System的统计逻辑如下已给出。
tab1中每项的内存占用
对内存进行的一般依据tab1的数据,每一个小项的内存值为 Pss Total + SwapPss,如上图中,每一项对应的内存值如下:
Name(每一块的内存分布) | Pss Total | SwapPss Dirty | 总占用内存 |
Native Heap | 18861 | 17706 | 36567 |
Dalvik Heap | 6016 | 51 | 6067 |
Stack | 924 | 660 | |
...... | ...... | ...... | ...... |
EGL mtrack | 44560 | 0 | 44560 |
GL mtrack | 5840 | 0 | 5840 |
(EGL多为应用在前台所占用的内存)
System内存
System = Total SwapPss + 共享内存;
(注:System=Total Pss - Total Private Clean - Total Private Dirty,而Total Pss = 各个部分的PSS值 + SwapPSS Dirty,顾可以将System转换成上式;)
(注:SwapPss 表示相关内存回收至Swap,该内存并未释放,如图中数据:
这里Native Heap 中的Pss total为18861并未包含SwapPss 11706,可以认为Native Heap实际总占用内存为 Pss Total + SwapPss Dirty,这里SwapPss占用过多的依然需要对Native Heap进行拆解 )
Graphics内存
Graphics = EGL mtrack + GL mtrack + Gfx dev(该块Mtk机型没有)
一般情况下EGL和GL占用的内存会比较多,需要对这两块进行分析和拆解,拆解方式在下文详解
-
Profiler查看内存
Android Studio会自带Profiler,可以使用手机对问题进行本地复现,或是导入一个 HPROF (.hprof
) 文件,大自查看当时的内存状态,详细可查看官方文档:
https://developer.android.com/studio/profile/memory-profiler?hl=zh-cn#capture-heap-dump
(可以利用该工具大致查看内存占用和对象的持有关系,可结合mat工具进行查看)
二. 常见内存拆解
1.Native Heap内存拆解
通用查看native heap部分内存可用以下方式,通常会需要与对比机进行对比,以确认多出的调用内存
http://t.csdnimg.cn/XJjAX
http://t.csdnimg.cn/NqKqS
2.Graphics内存拆解
一般情况下占用过大的是EGL和GL部分的内存
MTK平台
-
EGL
一般情况下应用在后台时,EGL内存值为0,dumpsys meminfo <pid>中不会包含EGL mtrack,如
在kernel-5.10之前(查看kernel版本方法 adb shell cat /proc/version),EGL统计的是ION memory,可以通过节点 /proc/ion/ion_mm_heap 大自看出ION内存的出处,如下:
//显示了各个进程和ion占用大小,会列出的各别进程使用的ion内存,但有重复包含share部分
client( dbg_name) pid size(cnt)--size(cnt) address threshold
----------------------------------------------------
time 1 9611282 ms
ndroid.settings( gralloc) 26696 56262656(8)--56262656(8) 0x0000000027997a3d 1073741824
iui.miwallpaper( gralloc) 1897 20373504(2)--20373504(2) 0x000000003957d3fd 1073741824
ndroid.systemui( gralloc) 2101 9945088(21)--9945088(21) 0x000000002729ee06 1073741824
system_server( gralloc) 1436 14745600(4)--14745600(4) 0x00000000ebaab696 1073741824
surfaceflinger( gralloc) 742 153645056(38)--153645056(38) 0x000000003d0e9fb1 1073741824
com.miui.home( gralloc) 2103 57532416(14)--57532416(14) 0x0000000079511560 1073741824
composer@2.1-se( gralloc) 681 109453312(26)--109453312(26) 0x00000000bc190eb0 1073741824
display( from_kernel) 1 35995648(8)--35995648(8) 0x000000007adc8128 1073741824
disp_decouple( from_kernel) 256 7581696(1)--7581696(1) 0x0000000072ff9cec 1073741824
......
//显示了应用的每个buffer
client(0x00000000ebaab696) system_server (gralloc) pid(1436) ================>
handle=0x000000003fe10505 (id: 3), buffer=0x000000003f3049f3, heap=10, fd= 738, ts: 9606350ms (1)
handle=0x00000000fbf85599 (id: 4), buffer=0x00000000c4044948, heap=10, fd= 740, ts: 9606570ms (2)
handle=0x000000003140ccc8 (id: 1), buffer=0x0000000029dc229f, heap=10, fd= 726, ts: 9564768ms (3)
handle=0x000000001fb4bd05 (id: 2), buffer=0x000000000d7fabfd, heap=10, fd= 734, ts: 9564797ms (4)
前台EGL mtrack出现占用过大的情况时,可先看一下该节点中的buffer占用,以及buffer释放时间(有些buffer若释放不及时会出现累加,EGL就会出现峰值,一般在动画或是图片多的场景下会出现峰值)
在kernel-5.10之后的EGL内存统计的是dmabuf,统计逻辑出现修改,且存在单个进程内存统计不准确的情况,后续补充分析方法。
先摆出结论:在VMWare虚拟机平台上,宿主机可以通过一个叫Balloon driver(vmware_balloon module)的驱动程序模拟客户机linux内部的进程占用内存,被占用的内存实际上是被宿主机调度到其他客户机去了。 但这种驱动程序模拟的客户机进程在linux上的内存动态分配并没有被linux内核统计进来,于是造成了上述问题的现象。
三、问题描述
通过free -h
或top
查看内存消耗,发现used已接近最大可用内存,但各进程常驻内存(RES)远比used要小。
-
GL:该部分内存的统计可查看 /sys/kernel/debug/mali0/ctx/<PID_X>/mem_profile节点,如
-
//2101_5中的 5 需要进入/sys/kernel/debug/mali0/ctx/目录后才能看到 lancelot:/ # cat /sys/kernel/debug/mali0/ctx/2101_5/mem_profile Channel: Default Heap (Total memory: 5166136) 13: 9 / 38352 14: 1 / 12480 18: 12 / 2361888 19: 8 / 2097152 20: 1 / 656264 Channel: Texture (Total memory: 45954240) 14: 9 / 100928 15: 7 / 157824 16: 1 / 33280 18: 1 / 208896 19: 5 / 1626112 20: 2 / 1368064 21: 2 / 3465216 22: 3 / 7987200 23: 2 / 9388032 24: 2 / 21618688 ......
该节点中的每个字段的意思如下:
-
一般只需知道Total memory后的值为每类buffer占用的总值,该节点信息可排相关内存。
高通平台
-
EGL和Gl通常都可使用/d/kgsl/proc/<pid>/mem查看底层内存块,如:
-
gpuaddr useraddr size id flags type usage sglen mapcnt eglsrf eglimg inode 0000000000000000 0000000000000000 196608 1 --w---N-- gpumem any(0) 0 0 0 0 0 0000000000000000 0000000000000000 16384 2 --w---Y-- gpumem command 0 1 0 0 0 0000000000000000 0000000000000000 4096 3 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 4096 4 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 4096 5 --w---Y-- gpumem gl 0 1 0 0 0 0000000000000000 0000000000000000 4096 6 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 4096 7 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 20480 8 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 4096 9 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 4096 10 --w---Y-- gpumem any(0) 0 1 0 0 0 0000000000000000 0000000000000000 196608 11 --w---N-- gpumem any(0) 0 0 0 0 0 ... 0000000000000000 0000000000000000 10444800 71 --wLb-N-- ion egl_image 165 0 0 1 531162
EGL过大时,可查看type为ion的内存块是否占用过多
GL和Gfx过大时,可查看type为gpumem的内存块是否占用过多
3. System内存拆解
System
其为 共享内存 + SwapPss
从dumpsys meminfo中可以看到Total SwapPss的值,例如
-
Pss Private Private SwapPss Rss Heap Heap Heap Total Dirty Clean Dirty Total Size Alloc Free ------ ------ ------ ------ ------ ------ ------ ------ Native Heap 9403 9104 272 12602 9824 23796 22437 1358 Dalvik Heap 10529 10468 0 1215 11000 14810 7405 7405 Dalvik Other 3572 3068 4 207 4564 Stack 772 772 0 404 772 Ashmem 26 0 0 0 552 Other dev 35 0 28 0 428 .so mmap 3188 220 28 211 31964 .jar mmap 2698 0 180 0 28208 .apk mmap 6683 0 1204 0 14764 .ttf mmap 65 0 0 0 236 .dex mmap 6963 0 6940 4 7464 .oat mmap 877 0 12 0 13588 .art mmap 2639 1924 24 444 10444 Other mmap 1640 36 888 0 5036 EGL mtrack 10323 10323 0 0 10323 GL mtrack 14214 14214 0 0 14214 Unknown 133 128 4 410 188 TOTAL 89257 50257 9584 15497 163569 38606 29842 8763 App Summary Pss(KB) Rss(KB) ------ ------ Java Heap: 12416 21444 Native Heap: 9104 9824 Code: 8584 97220 Stack: 772 772 Graphics: 24537 24537 Private Other: 4428 System: 29416 Unknown: 9772 TOTAL PSS: 89257 TOTAL RSS: 163569 TOTAL SWAP PSS: 15497
从dumpsys meminfo的数据中可以看到,Total SwapPss为15497,而Native Heap中的SwapPss为12602,这块内存只是系统将暂时未用到的内存回收至swap区,依然需要从Native Heap入手进行拆解。
共享内存
是对某块内存进行共享使用,现未发现有较大占用的情况,若需要计算每块共享内存,可使用每一项的Pss Total - Private Dirty - Private Clean ,再寻找某一项的问题,如上述数据中 .apk mmap共享内存为6683 - 0 - 1204 = 5479
4. smaps详解
读取/proc/pid/smaps节点的信息,如smaps,下表为smaps的注解,除了EGL和GL的内存,其他内存都会反应在smaps中,如:
-
701b3000-701b4000 r--p 00003000 //虚拟内存段的开始和结束位置 内存段的权限 该虚拟内存段起始地址在对应的映射文件中以页为单位的偏移量 fd:13 51 /apex/com.android.art/javalib/arm64/boot-apache-xml.oat //文件的主设备号和次设备号 被映射到虚拟内存的文件的索引节点号 被映射到虚拟内存的文件名称 Size: 4 kB //虚拟内存空间大小 KernelPageSize: 4 kB //内核一页的大小 MMUPageSize: 4 kB //MMU页大小 //实际分配的内存 Rss=Shared_Clean+Shared_Dirty+Private_Clean+Private_Dirty Rss: 0 kB //是平摊计算后的实际物理使用内存 Pss=private_clean+private_dirty+按比例均分的shared_clean、shared_dirty。 Pss: 0 kB Shared_Clean: 0 kB Shared_Dirty: 0 kB Private_Clean: 0 kB Private_Dirty: 0 kB Referenced: 0 kB //当前页面被标记为已引用或者包含匿名映射 Anonymous: 0 kB //匿名映射的物理内存 LazyFree: 0 kB AnonHugePages: 0 kB ShmemPmdMapped: 0 kB //PMD页面已经被映射的共享(shmem / tmpfs)内存量 FilePmdMapped: 0 kB //由hugetlbfs页面支持的内存使用量 Shared_Hugetlb: 0 kB Private_Hugetlb: 0 kB Swap: 0 kB //存在于交换分区的数据大小 SwapPss: 0 kB //逻辑就跟pss一样,针对的是Swap的内存 Locked: 0 kB THPeligible: 0 //映射是否符合分配THP的条件 VmFlags: rd mr mw me //表示与特定虚拟内存区域关联的内核标志
脚本:
check_smaps.py
使用方法:
-
#!/usr/bin/env python3 from collections import namedtuple from sys import argv, stdin import re fd = stdin if len(argv) != 2 else open(argv[1], 'r') # Reference: # file: kernel/msm-4.9/fs/proc/task_mmu.c # func: show_map_vma RE_MAPPING_LINE = re.compile( '(?P<begin_addr>[a-f0-9]+)-(?P<end_addr>[a-f0-9]+)' ' (?P<permission>....) (?P<pgoff>[a-f0-9]+)' ' (?P<major_dev>[a-f0-9]+):(?P<minor_dev>[a-f0-9]+)' ' (?P<ino>\d+)( *(?P<name>.*))?' ) PSS_LINE = "^Pss: *([0-9]+) kB" Entry = namedtuple('Entry', ['name', 'num', 'max', 'sum']) class Entry: def __init__(self, name, num, max, sum): self.name = name self.num = num self.max = max self.sum = sum def __lt__(self, other): return self.sum > other.sum stats = dict() for line in fd: line = line.rstrip() r = RE_MAPPING_LINE.match(line) if r: name = r.groupdict()['name'] if not name: name = '(NONAME)' flag = 1 continue if flag: pss = re.match(PSS_LINE, line) if pss: flag = 0 size = int(pss.group(1)) if size: if name in stats: stats[name].num += 1 stats[name].max = max(size, stats[name].max) stats[name].sum += size print("name:{}, num:{}, max:{}, sum:{}".format(name, stats[name].num, stats[name].max, stats[name].sum)) else: entry = Entry(name, 1, size, size) stats[name] = entry print("name:{}, num:{}, max:{}, sum:{}".format(name, stats[name].num, stats[name].max, stats[name].sum)) ''' if not r: print('Invalid lines "{}"'.format(line)) continue size = int(r.groupdict()['end_addr'], 16) - int(r.groupdict()['begin_addr'], 16) name = r.groupdict()['name'] if not name: name = '(NONAME)' if name.startswith('[stack'): name = '[stack:*]' if name in stats: stats[name].num += 1 stats[name].max = max(size, stats[name].max) stats[name].sum += size else: entry = Entry(name, 1, size, size) stats[name] = entry mmap = dict() for f in sorted([_[1] for _ in stats.items()]): out = f.name.split(".")[-1] if(out == "so"): mmap[name]=".so mmap" if f.name in mmap: mmap[name].name = f.name mmap[name].num += 1 mmap[name].max = max(f.size, mmap[name].max) mmap[name].sum += size else: entry = Entry(name, 1, size, size) mmap[name] = entry elif (out == "jar"): elif (out == ".odex" or out == ".dex" or out == ".vdex"): else: continue ''' format_f = '{:<120} {:<12} {:<12} {:<12}'.format print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) print('-' * 79) for foo in sorted([_[1] for _ in stats.items()]): print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print('-' * 79) print(format_f('Total', sum([_[1].num for _ in stats.items()]), max([_[1].max for _ in stats.items()]), sum([_[1].sum for _ in stats.items()]), )) print("{}{}{}".format('-' * 40, "dev", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_dev = 0 for foo in sorted([_[1] for _ in stats.items()]): if len(foo.name.split("/")) >= 2: out = foo.name.split("/")[1] if (out == "dev"): sum_dev = sum_dev + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_dev)) print("{}{}{}".format('-' * 40, "so", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_so = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "so"): sum_so = sum_so + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_so)) print("{}{}{}".format('-' * 40, "jar", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_jar = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "jar"): sum_jar = sum_jar + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_jar)) print("{}{}{}".format('-' * 40, "apk", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_apk = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "apk"): sum_apk = sum_apk + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_apk)) print("{}{}{}".format('-' * 40, "ttf", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_ttf = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "ttf"): sum_ttf = sum_ttf + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_ttf)) print("{}{}{}".format('-' * 40, "dex", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_dex = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "odex" or out == "dex" or out == "vdex"): sum_dex = sum_dex + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_dex)) print("{}{}{}".format('-' * 40, "oat", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_oat = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "oat"): sum_oat = sum_oat + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_oat)) print("{}{}{}".format('-' * 40, "art", '-' * 40)) print(format_f('Name', 'Num(count)', 'Max(KB)', 'Sum(KB)')) sum_oat = 0 for foo in sorted([_[1] for _ in stats.items()]): out = foo.name.split(".")[-1] if (out == "art" or out=="art]"): sum_oat = sum_oat + foo.sum print(format_f(foo.name[:120], foo.num, foo.max, foo.sum)) print(format_f('Sum', '', '', sum_oat))
Adb pull /proc/<pid>/smaps .
python check_smaps.py smaps
可筛选smaps中的信息,smaps中只能显示出哪一块内存比较大,其他细项依然需要从其他角度定位。
-
学习拆解MEMINFO
一、背景
近期在公司的某台linux虚拟机上,发现内存几乎消耗殆尽,但找不到其去向。 在调查过程中,重点分析了/proc/meminfo文件,对其内存占用进行了学习与分析。
-
二、环境
-
虚拟机OS : CentOS Linux release 7.4.1708 (Core)
-
虚拟机平台 : VMWare
-
三、问题描述
通过
free -h
或top
查看内存消耗,发现used已接近最大可用内存,但各进程常驻内存(RES)远比used要小。先摆出结论:在VMWare虚拟机平台上,宿主机可以通过一个叫Balloon driver(vmware_balloon module)的驱动程序模拟客户机linux内部的进程占用内存,被占用的内存实际上是被宿主机调度到其他客户机去了。 但这种驱动程序模拟的客户机进程在linux上的内存动态分配并没有被linux内核统计进来,于是造成了上述问题的现象。
3.1 top 结果
按内存消耗排序,取消耗大于0的部分
top - 16:46:45 up 8 days, 10:25, 1 user, load average: 0.00, 0.01, 0.05
Tasks: 109 total, 1 running, 108 sleeping, 0 stopped, 0 zombie
%Cpu(s): 0.1 us, 0.0 sy, 0.0 ni, 99.9 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
KiB Mem : 7994080 total, 185776 free, 7625996 used, 182308 buff/cache
KiB Swap: 4157436 total, 294944 free, 3862492 used. 115964 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND DATA
3725 root 20 0 9057140 1.734g 5020 S 0.3 22.7 367:48.86 java 8882680
1087 mysql 20 0 2672240 233064 1076 S 0.0 2.9 102:33.71 mysqld 2596840
496 root 20 0 36828 3512 3388 S 0.0 0.0 0:31.13 systemd-journal 356
14564 root 20 0 145700 2424 1148 S 0.0 0.0 0:02.94 sshd 924
1 root 20 0 128164 2404 724 S 0.0 0.0 1:02.18 systemd 84628
14713 root 20 0 157716 2204 1512 R 0.0 0.0 0:00.08 top 1176
14568 root 20 0 115524 1784 1272 S 0.0 0.0 0:00.59 bash 632
687 root 20 0 305408 1548 1168 S 0.0 0.0 13:59.34 vmtoolsd 75352
676 root 20 0 216388 1240 872 S 0.0 0.0 1:56.69 rsyslogd 148768
682 root 20 0 472296 908 160 S 0.0 0.0 1:06.73 NetworkManager 222852
684 root 20 0 24336 752 444 S 0.0 0.0 0:22.19 systemd-logind 504
690 polkitd 20 0 534132 560 220 S 0.0 0.0 0:07.34 polkitd 450080
677 dbus 20 0 32772 460 128 S 0.0 0.0 0:08.34 dbus-daemon 8900
688 root 20 0 21620 452 296 S 0.0 0.0 4:42.68 irqbalance 488
698 root 20 0 126232 432 328 S 0.0 0.0 0:30.25 crond 1312
922 root 20 0 562392 412 28 S 0.0 0.0 4:52.69 tuned 304472
924 root 20 0 105996 188 92 S 0.0 0.0 0:03.64 sshd 760
653 root 16 -4 55452 84 0 S 0.0 0.0 0:08.81 auditd 8664
532 root 20 0 46684 4 4 S 0.0 0.0 0:02.81 systemd-udevd 1916
705 root 20 0 110044 4 4 S 0.0 0.0 0:00.02 agetty 344
3.2 top结果第四行内存总体使用情况
属性 | 大小(G) | 说明 |
total | 7.6 | 可分配内存总计值 |
used | 7.26 | 已分配内存 |
free | 0.17 | 未分配内存 |
buff/cache | 0.17 | buff与缓存 |
-
各属性满足公式:total = used + free + buff/cache
-
used在该linux版本(centos7)上,已经反映实际分配的内存,不需要再去除buff/cache部分
3.3 top进程列表内存相关列统计
列 | 合计(G) | 说明 |
VIRT | 14.236 | 进程申请的虚拟内存大小,申请不意味着分配,该值与实际内存消耗关系不大。 |
RES | 1.9747 | 进程常驻内存,包含进程间共享内存。 |
SHR | 0.0171 | 进程间共享内存,该值是推算出来的,存在误差,意义不大。 |
3.4 问题来了
RES合计值比used少了5G多!这些内存哪去了?
理论上,各进程的RES合计值因为会重复计算共享内存,应该比used值略大。实际上这两个值也往往是接近的,不应该差这么多。
四、清查linux内存消耗
为了进一步检查linux中内存消耗的去向,需要对/proc/meminfo
文件进行一次彻底的分析统计。
linux上各种内存查看工具如free,top实际上都是从/proc下面找linux内核的各种统计文件。
4.1 /proc/meminfo内容
MemTotal: 7994080 kB
MemFree: 125256 kB
MemAvailable: 932412 kB
Buffers: 8 kB
Cached: 993796 kB
SwapCached: 252 kB
Active: 1182220 kB
Inactive: 1213960 kB
Active(anon): 693796 kB
Inactive(anon): 717156 kB
Active(file): 488424 kB
Inactive(file): 496804 kB
Unevictable: 0 kB
Mlocked: 0 kB
SwapTotal: 4157436 kB
SwapFree: 4157172 kB
Dirty: 8 kB
Writeback: 0 kB
AnonPages: 1402140 kB
Mapped: 41584 kB
Shmem: 8576 kB
Slab: 143220 kB
SReclaimable: 86720 kB
SUnreclaim: 56500 kB
KernelStack: 5360 kB
PageTables: 7184 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 8154476 kB
Committed_AS: 2073776 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 191584 kB
VmallocChunk: 34359310332 kB
HardwareCorrupted: 0 kB
AnonHugePages: 1284096 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 89920 kB
DirectMap2M: 4104192 kB
DirectMap1G: 6291456 kB
4.2 meminfo内容分析
属性 | 大小(k) | 说明 | 扩展说明 |
MemTotal: | 7994080 | 可供linux内核分配的内存总量。 | 比物理内存总量少一点,因为主板/固件会保留一部分内存、linux内核自己也会占用一部分内存。 |
MemFree: | 125256 | 表示系统尚未分配的内存。 | |
MemAvailable: | 932412 | 当前可用内存。 | MemFree只是尚未分配的内存,并不是所有可用的内存。有些已经分配掉的内存是可以回收再分配的。比如cache/buffer、slab都有一部分是可以回收的,这部分可回收的内存加上MemFree才是系统可用的内存,即MemAvailable。同时要注意,MemAvailable是内核使用特定的算法估算出来的,并不精确。 |
Buffers: | 8 | 块设备(block device)所占用的特殊file-backed pages,包括:直接读写块设备,以及文件系统元数据(metadata)比如superblock使用的缓存页。 | Buffers内存页同时也在LRU list中,被统计在Active(file)或Inactive(file)之中。 |
Cached: | 993796 | 所有file-backed pages | 用户进程的内存页分为两种:file-backed pages(与文件对应的内存页),和anonymous pages(匿名页),比如进程的代码、映射的文件都是file-backed,而进程的堆、栈都是不与文件相对应的、就属于匿名页。file-backed pages在内存不足的时候可以直接写回对应的硬盘文件里,称为page-out,不需要用到交换区(swap);而anonymous pages在内存不足时就只能写到硬盘上的交换区(swap)里,称为swap-out。 |
SwapCached: | 252 | SwapCached包含的是被确定要swap-out,但是尚未写入交换区的匿名内存页。 | SwapCached内存页会同时被统计在LRU或AnonPages或Shmem中,它本身并不占用额外的内存。 |
Active: | 1182220 | active包含active anon和active file | LRU是一种内存页回收算法,Least Recently Used,最近最少使用。LRU认为,在最近时间段内被访问的数据在以后被再次访问的概率,要高于最近一直没被访问的页面。于是近期未被访问到的页面就成为了页面回收的第一选择。Linux kernel会记录每个页面的近期访问次数,然后设计了两种LRU list: active list 和 inactive list, 刚访问过的页面放进active list,长时间未访问过的页面放进inactive list,回收内存页时,直接找inactive list即可。另外,内核线程kswapd会周期性地把active list中符合条件的页面移到inactive list中。 |
Inactive: | 1213960 | inactive包含inactive anon和inactive file | |
Active(anon): | 693796 | 活跃匿名页,anonymous pages(匿名页)。 | |
Inactive(anon): | 717156 | 非活跃匿名页 | |
Active(file): | 488424 | 活跃文件内存页 | |
Inactive(file): | 496804 | 非活跃文件内存页 | |
Unevictable: | 0 | 因为种种原因无法回收(page-out)或者交换到swap(swap-out)的内存页 | Unevictable LRU list上是不能pageout/swapout的内存页,包括VM_LOCKED的内存页、SHM_LOCK的共享内存页(同时被统计在Mlocked中)、和ramfs。在unevictable list出现之前,这些内存页都在Active/Inactive lists上,vmscan每次都要扫过它们,但是又不能把它们pageout/swapout,这在大内存的系统上会严重影响性能,unevictable list的初衷就是避免这种情况的发生。 |
Mlocked: | 0 | 被系统调用"mlock()"锁定到内存中的页面。Mlocked页面是不可收回的。 | 被锁定的内存因为不能pageout/swapout,会从Active/Inactive LRU list移到Unevictable LRU list上。Mlocked与以下统计项重叠:LRU Unevictable,AnonPages,Shmem,Mapped等。 |
SwapTotal: | 4157436 | swap空间总计 | |
SwapFree: | 4157172 | 当前剩余swap | |
Dirty: | 8 | 需要写入磁盘的内存页的大小 | Dirty并不包括系统中全部的dirty pages,需要再加上另外两项:NFS_Unstable 和 Writeback,NFS_Unstable是发给NFS server但尚未写入硬盘的缓存页,Writeback是正准备回写硬盘的缓存页。 |
Writeback: | 0 | 正在被写回的内存页的大小 | |
AnonPages: | 1402140 | Anonymous pages(匿名页)数量 + AnonHugePages(透明大页)数量 | 进程所占的内存页分为anonymous pages和file-backed pages,理论上,所有进程的PSS之和 = Mapped + AnonPages。PSS是Proportional Set Size,每个进程实际使用的物理内存(比例分配共享库占用的内存),可以在/proc/[1-9]*/smaps中查看。 |
Mapped: | 41584 | 正被用户进程关联的file-backed pages | Cached包含了所有file-backed pages,其中有些文件当前不在使用,但Cached仍然可能保留着它们的file-backed pages;而另一些文件正被用户进程关联,比如shared libraries、可执行程序的文件、mmap的文件等,这些文件的缓存页就称为mapped。 |
Shmem: | 8576 | Shmem统计的内容包括:1.shared memory;2.tmpfs和devtmpfs。所有tmpfs类型的文件系统占用的空间都计入共享内存,devtmpfs是/dev文件系统的类型,/dev/下所有的文件占用的空间也属于共享内存。可以用ls和du命令查看。如果文件在没有关闭的情况下被删除,空间仍然不会释放,shmem不会减小,可以用 lsof -a +L1 /<mount_point> 命令列出这样的文件。 | shared memory被视为基于tmpfs文件系统的内存页,既然基于文件系统,就不算匿名页,所以不被计入/proc/meminfo中的AnonPages,而是被统计进了:Cached或Mapped(当shmem被attached时候)。然而它们背后并不存在真正的硬盘文件,一旦内存不足的时候,它们是需要交换区才能swap-out的,所以在LRU lists里,它们被放在Inactive(anon) 或 Active(anon)或 unevictable (如果被locked的话)里。注意:/proc/meminfo中的 Shmem 统计的是已经分配的大小,而不是创建时申请的大小。 |
Slab: | 143220 | 通过slab分配的内存,Slab=SReclaimable+SUnreclaim | slab是linux内核的一种内存分配器。linux内核的动态内存分配有以下几种方式:1.alloc_pages/__get_free_page:以页为单位分配。2.vmalloc:以字节为单位分配虚拟地址连续的内存块。3.slab:对小对象进行分配,不用为每个小对象分配一个页,节省了空间;内核中一些小对象创建析构很频繁,Slab对这些小对象做缓存,可以重复利用一些相同的对象,减少内存分配次数。4.kmalloc:以slab为基础,以字节为单位分配物理地址连续的内存块。 |
SReclaimable: | 86720 | slab中可回收的部分。 | |
SUnreclaim: | 56500 | slab中不可回收的部分。 | |
KernelStack: | 5360 | 给用户线程分配的内核栈消耗的内存页 | 每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或异常(exception)进入内核态的时候才会用到,也就是说内核栈是给kernel code使用的。在x86系统上Linux的内核栈大小是固定的8K或16K。Kernel stack(内核栈)是常驻内存的,既不包括在LRU lists里,也不包括在进程的RSS/PSS内存里。RSS是Resident Set Size 实际使用物理内存(包含共享库占用的内存),可以在/proc/[1-9]*/smaps中查看。 |
PageTables: | 7184 | Page Table的消耗的内存页 | Page Table的用途是翻译虚拟地址和物理地址,它是会动态变化的,要从MemTotal中消耗内存。 |
NFS_Unstable: | 0 | 发给NFS server但尚未写入硬盘的缓存页 | |
Bounce: | 0 | bounce buffering消耗的内存页 | 有些老设备只能访问低端内存,比如16M以下的内存,当应用程序发出一个I/O 请求,DMA的目的地址却是高端内存时(比如在16M以上),内核将在低端内存中分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处。 |
WritebackTmp: | 0 | 正准备回写硬盘的缓存页 | |
CommitLimit: | 8154476 | overcommit阈值,CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + Swap | Linux是允许memory overcommit的,即承诺给进程的内存大小超过了实际可用的内存。commit(或overcommit)针对的是内存申请,内存申请不等于内存分配,内存只在实际用到的时候才分配。但可以申请的内存有个上限阈值,即CommitLimit,超出以后就不能再申请了。 |
Committed_AS: | 2073776 | 所有进程已经申请的内存总大小 | |
VmallocTotal: | 34359738367 | 可分配的虚拟内存总计 | |
VmallocUsed: | 191584 | 已通过vmalloc分配的内存,不止包括了分配的物理内存,还统计了VM_IOREMAP、VM_MAP等操作的值 | VM_IOREMAP是把IO地址映射到内核空间、并未消耗物理内存 |
VmallocChunk: | 34359310332 | 通过vmalloc可分配的虚拟地址连续的最大内存 | |
HardwareCorrupted: | 0 | 因为内存的硬件故障而删除的内存页 | |
AnonHugePages: | 1284096 | AnonHugePages统计的是Transparent HugePages (THP),THP与Hugepages不是一回事,区别很大。Hugepages在/proc/meminfo中是被独立统计的,与其它统计项不重叠,既不计入进程的RSS/PSS中,又不计入LRU Active/Inactive,也不会计入cache/buffer。如果进程使用了Hugepages,它的RSS/PSS不会增加。而AnonHugePages完全不同,它与/proc/meminfo的其他统计项有重叠,首先它被包含在AnonPages之中,而且在/proc/<pid>/smaps中也有单个进程的统计,与进程的RSS/PSS是有重叠的,如果用户进程用到了THP,进程的RSS/PSS也会相应增加,这与Hugepages是不同的。 | Transparent Huge Pages 缩写 THP ,这个是 RHEL 6 开始引入的一个功能,在 Linux6 上透明大页是默认启用的。由于 Huge pages 很难手动管理,而且通常需要对代码进行重大的更改才能有效的使用,因此 RHEL 6 开始引入了 Transparent Huge Pages ( THP ), THP 是一个抽象层,能够自动创建、管理和使用传统大页。THP 为系统管理员和开发人员减少了很多使用传统大页的复杂性 , 因为 THP 的目标是改进性能 , 因此其它开发人员 ( 来自社区和红帽 ) 已在各种系统、配置、应用程序和负载中对 THP 进行了测试和优化。这样可让 THP 的默认设置改进大多数系统配置性能。但是 , 不建议对数据库工作负载使用 THP 。这两者最大的区别在于 : 标准大页管理是预分配的方式,而透明大页管理则是动态分配的方式。 |
HugePages_Total: | 0 | 预分配的可使用的标准大页池的大小。HugePages在内核中独立管理,只要一经定义,无论是否被使用,都不再属于free memory。 | Huge pages(标准大页) 是从 Linux Kernel 2.6 后被引入的,目的是通过使用大页内存来取代传统的 4kb 内存页面, 以适应越来越大的系统内存,让操作系统可以支持现代硬件架构的大页面容量功能。 |
HugePages_Free: | 0 | 标准大页池中尚未分配的标准大页 | |
HugePages_Rsvd: | 0 | 用户程序预申请的标准大页,尚未真的分配走 | |
HugePages_Surp: | 0 | 标准大页池的盈余 | |
Hugepagesize: | 2048 | 标准大页大小,这里是2M | |
DirectMap4k: | 89920 | 映射为4kB的内存数量 | DirectMap所统计的不是关于内存的使用,而是一个反映TLB效率的指标。TLB(Translation Lookaside Buffer)是位于CPU上的缓存,用于将内存的虚拟地址翻译成物理地址,由于TLB的大小有限,不能缓存的地址就需要访问内存里的page table来进行翻译,速度慢很多。为了尽可能地将地址放进TLB缓存,新的CPU硬件支持比4k更大的页面从而达到减少地址数量的目的, 比如2MB,4MB,甚至1GB的内存页,视不同的硬件而定。所以DirectMap其实是一个反映TLB效率的指标。 |
DirectMap2M: | 4104192 | 映射为2MB的内存数量 | |
DirectMap1G: | 6291456 | 映射为1GB的内存数量 |
4.3 根据meminfo统计内存占用
linux上的内存消耗总的来说有两部分,一部分是内核kernel进程,另一部分是用户进程。因此我们统计这两部分进程的内存消耗再相加即可。
-
内核部分:
Slab+ VmallocUsed + PageTables + KernelStack + HardwareCorrupted + Bounce + X
X是指linux内核没有统计进来的,动态内存分配中通过alloc_pages分配的内存。
http://linuxperf.com/?cat=7
就指出了一个这样的例子: 在VMware guest上有一个常见问题,就是VMWare ESX宿主机会通过guest上的Balloon driver(vmware_balloon module)占用guest的内存,有时占用得太多会导致guest无内存可用,这时去检查guest的/proc/meminfo只看见MemFree很少、但看不出内存的去向,原因就是Balloon driver通过alloc_pages分配内存,没有在/proc/meminfo中留下统计值,所以很难追踪。
-
用户进程部分:
Active + Inactive + Unevictable + (HugePages_Total * Hugepagesize)
或Cached + AnonPages + Buffers + (HugePages_Total * Hugepagesize)
根据上述公式,除掉X部分,得出linux内核统计出来的已分配内存为:2.62G。
该值远小于使用
free
或top
得到的used。 这里推测原因就是linux没有统计进来的alloc_pages分配的内存。 考虑到该linux确实是在VMWare平台上申请的虚拟机,因此我们推测是由于虚拟机平台内存不足,于是宿主机模拟该客户机内部进程消耗内存,实际上将内存调度到其他虚客户机去了。
五、结论
虚拟机管理员尚未最终确认,目前仅仅是推测
在VMWare虚拟机平台上,宿主机可以通过一个叫Balloon driver(vmware_balloon module)的驱动程序模拟客户机linux内部的进程占用内存,被占用的内存实际上是被宿主机调度到其他客户机去了。 但这种驱动程序模拟的客户机进程在客户机linux上是通过alloc_pages实现的内存动态分配,并没有被linux内核统计进来,于是造成了内存去向不明的现象。
原文章:
https://zhuanlan.zhihu.com/p/575366699
https://www.cnblogs.com/bakari/p/10486818.html
Sample数据分析:
使用方法:
命令:
python smaps_parser.py -f <name >
https://github.com/Gracker/Android-App-Memory-Analysis
脚本源码:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#@Time : 2019/6/13 上午10:55
#@Author: yangzhiting
#@File : parse.py
import argparse
import re
from collections import Counter
import os
import subprocess
type_length = 17
pssSum_count = [0] * type_length
pss_count = [0] * type_length
swapPss_count = [0] * type_length
HEAP_UNKNOWN = 0
HEAP_DALVIK = 1
HEAP_NATIVE = 2
HEAP_DALVIK_OTHER = 3
HEAP_STACK = 4
HEAP_CURSOR = 5
HEAP_ASHMEM = 6
HEAP_GL_DEV = 7
HEAP_UNKNOWN_DEV = 8
HEAP_SO = 9
HEAP_JAR = 10
HEAP_APK = 11
HEAP_TTF = 12
HEAP_DEX = 13
HEAP_OAT = 14
HEAP_ART = 15
HEAP_UNKNOWN_MAP = 16
HEAP_GRAPHICS = 17
HEAP_GL = 18
HEAP_OTHER_MEMTRACK = 19
# Dalvik extra sections (heap)
HEAP_DALVIK_NORMAL = 20
HEAP_DALVIK_LARGE = 21
HEAP_DALVIK_ZYGOTE = 22
HEAP_DALVIK_NON_MOVING = 23
# Dalvik other extra sections.
HEAP_DALVIK_OTHER_LINEARALLOC = 24
HEAP_DALVIK_OTHER_ACCOUNTING = 25
HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE = 26
HEAP_DALVIK_OTHER_APP_CODE_CACHE = 27
HEAP_DALVIK_OTHER_COMPILER_METADATA = 28
HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE = 29
# Boot vdex / app dex / app vdex
HEAP_DEX_BOOT_VDEX = 30
HEAP_DEX_APP_DEX = 31
HEAP_DEX_APP_VDEX = 32
# App art, boot art.
HEAP_ART_APP = 33
HEAP_ART_BOOT = 34
_NUM_HEAP = 35
_NUM_EXCLUSIVE_HEAP = HEAP_OTHER_MEMTRACK+1
_NUM_CORE_HEAP = HEAP_NATIVE+1
#pss_type = ["HEAP_UNKNOWN", "HEAP_DALVIK", "HEAP_NATIVE", "HEAP_DALVIK_OTHER", "HEAP_STACK", "HEAP_CURSOR", "HEAP_ASHMEM", "HEAP_GL_DEV", \
# "HEAP_UNKNOWN_DEV", "HEAP_SO", "HEAP_JAR", "HEAP_APK", "HEAP_TTF", "HEAP_DEX", "HEAP_OAT", "HEAP_ART", "HEAP_UNKNOWN_MAP" ,\
# "HEAP_GRAPHICS","HEAP_GL","HEAP_OTHER_MEMTRACK",\
# "HEAP_DALVIK_NORMAL,"HEAP_DALVIK_LARGE","HEAP_DALVIK_ZYGOTE","HEAP_DALVIK_NON_MOVING" ,\
# "HEAP_DALVIK_OTHER_LINEARALLOC","HEAP_DALVIK_OTHER_ACCOUNTING","HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE","HEAP_DALVIK_OTHER_APP_CODE_CACHE","HEAP_DALVIK_OTHER_COMPILER_METADATA","HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE", \
# "HEAP_DEX_BOOT_VDEX", "HEAP_DEX_APP_DEX","HEAP_DEX_APP_VDEX", \
# "HEAP_ART_APP","HEAP_ART_BOOT" ]
pss_type = ["Unknown", "Dalvik", "Native", "Dalvik Other", "Stack", "Cursor", "Ashmem", "Gfx dev", \
"Other dev", ".so mmap", ".jar mmap", ".apk mmap", ".ttf mmap", ".dex mmap", ".oat mmap", ".art mmap", "Other mmap", \
"graphics","gl","other memtrack", \
"dalvik normal","dalvik large","dalvik zygote","dalvik non moving" ,\
"dalvik other lineralloc","dalvik other accounting","dalvik other zygote code cache","dalvik other app code cache","dalvik other compiler metadata","dalvik other indirect reference table" ,\
"dex boot vdex","dex app dex","dex app vdex", \
"heap art app","heap art boot"]
type_list = []
for i in range(type_length):
type_list.append({})
#制作提示
def help():
parse = argparse.ArgumentParser(description="smaps parser")
parse.add_argument('-p', '--pid', help="pid")
parse.add_argument('-f', '--filename', help="smaps file")
parse.add_argument('-t', '--type', help="Unknown, Dalvik, Native, Dalvik Other, Stack, Cursor, Ashmem, Gfx dev, \
Other dev, .so mmap, .jar mmap, .apk mmap, .ttf mmap, .dex mmap, .oat mmap, .art mmap, Other mmap", default="ALL")
parse.add_argument('-o', '--output', help="output file", default="smaps_analysis.txt")
parse.add_argument('-s', '--simple', action="store_true", help="simple output", default=False)
return parse.parse_args()
def match_head(line):
return re.match(r'(\w*)-(\w*) (\S*) (\w*) (\w*):(\w*) (\w*)\s*(.+)$', line, re.I)
def match_type(name, prewhat):
which_heap = HEAP_UNKNOWN
sub_heap = HEAP_UNKNOWN
is_swappable = False
if(name.endswith(" (deleted)")):
name = name[0 : len(name)- len(' (deleted)')]
size = len(name)
if name.startswith("[heap]"):
which_heap = HEAP_NATIVE
elif name.startswith("[anon:libc_malloc]"):
which_heap = HEAP_NATIVE
elif name.startswith("[anon:scudo:"):
which_heap = HEAP_NATIVE
elif name.startswith("[anon:GWP-ASan"):
which_heap = HEAP_NATIVE
elif name.startswith("[stack"):
which_heap = HEAP_STACK
elif name.startswith("[anon:stack_and_tls:"):
which_heap = HEAP_STACK
elif name.endswith(".so"):
which_heap = HEAP_SO
is_swappable = True
elif name.endswith(".jar"):
which_heap = HEAP_JAR
is_swappable = True
elif name.endswith(".apk"):
which_heap = HEAP_APK
is_swappable = True
elif name.endswith(".ttf"):
which_heap = HEAP_TTF
is_swappable = True
elif name.endswith(".odex") | (size > 4 and name.__contains__(".dex")) :
which_heap = HEAP_DEX
sub_heap = HEAP_DEX_APP_DEX
is_swappable = True
elif name.endswith(".vdex"):
which_heap = HEAP_DEX
# Handle system@framework@boot and system/framework/boot|apex
if name.__contains__("@boot") | name.__contains__("/boot") | name.__contains__("/apex"):
sub_heap = HEAP_DEX_BOOT_VDEX
else:
sub_heap = HEAP_DEX_APP_VDEX
is_swappable = True
elif name.endswith(".oat"):
which_heap = HEAP_OAT
is_swappable = True
elif name.endswith(".art") | name.endswith(".art]"):
which_heap = HEAP_ART
# Handle system@framework@boot* and system/framework/boot|apex*
if name.__contains__("@boot") | name.__contains__("/boot") | name.__contains__("/apex"):
sub_heap = HEAP_ART_BOOT
else:
sub_heap = HEAP_ART_APP
is_swappable = True
elif name.startswith("/dev"):
which_heap = HEAP_UNKNOWN_DEV
if name.startswith("/dev/kgsl-3d0"):
which_heap = HEAP_GL_DEV
elif name.__contains__("/dev/ashmem/CursorWindow"):
which_heap = HEAP_CURSOR
elif name.startswith("/dev/ashmem/jit-zygote-cache"):
which_heap = HEAP_DALVIK_OTHER
sub_heap = HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE
elif name.__contains__("/dev/ashmem"):
which_heap = HEAP_ASHMEM
elif name.startswith("/memfd:jit-cache"):
which_heap = HEAP_DALVIK_OTHER
sub_heap = HEAP_DALVIK_OTHER_APP_CODE_CACHE
elif name.startswith("/memfd:jit-zygote-cache"):
which_heap = HEAP_DALVIK_OTHER;
sub_heap = HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE;
elif name.startswith("[anon:"):
which_heap = HEAP_UNKNOWN
if name.startswith("[anon:dalvik-"):
which_heap = HEAP_DALVIK_OTHER
if name.startswith("[anon:dalvik-LinearAlloc"):
sub_heap = HEAP_DALVIK_OTHER_LINEARALLOC
elif name.startswith("[anon:dalvik-alloc space") | name.startswith("[anon:dalvik-main space"):
# This is the regular Dalvik heap.
which_heap = HEAP_DALVIK
sub_heap = HEAP_DALVIK_NORMAL
elif name.startswith("[anon:dalvik-large object space") | name.startswith("[anon:dalvik-free list large object space"):
which_heap = HEAP_DALVIK
sub_heap = HEAP_DALVIK_LARGE
elif name.startswith("[anon:dalvik-non moving space"):
which_heap = HEAP_DALVIK
sub_heap = HEAP_DALVIK_NON_MOVING
elif name.startswith("[anon:dalvik-zygote space"):
which_heap = HEAP_DALVIK
sub_heap = HEAP_DALVIK_ZYGOTE
elif name.startswith("[anon:dalvik-indirect ref"):
sub_heap = HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE
elif name.startswith("[anon:dalvik-jit-code-cache") | name.startswith("[anon:dalvik-data-code-cache"):
sub_heap = HEAP_DALVIK_OTHER_APP_CODE_CACHE
elif name.startswith("[anon:dalvik-CompilerMetadata"):
sub_heap = HEAP_DALVIK_OTHER_COMPILER_METADATA
else:
sub_heap = HEAP_DALVIK_OTHER_ACCOUNTING
elif not name.__eq__(" "):
if name.__sizeof__() > 0:
which_heap = HEAP_UNKNOWN_MAP
elif prewhat == 10:
which_heap = 10
return which_heap
def match_pss(line):
tmp = re.match('Pss:\s+([0-9]*) kB', line, re.I)
if tmp:
return tmp
def match_swapPss(line):
tmp = re.match('SwapPss:\s+([0-9]*) kB', line, re.I)
if tmp:
return tmp
def parse_smaps(filename):
file = open(filename, 'r')
line = file.readline()
if not line:
return
what = 0
prewhat = 0
while 1:
tmp = match_head(line)
if tmp:
name = tmp.group(8)
# print("name:" + name)
what = match_type(name, prewhat)
# print "name = " + name + "what = " + str(what)
while 1:
line2 = file.readline()
if not line2:
return
tmp2 = match_pss(line2)
tmp3 = match_swapPss(line2)
if tmp2 or tmp3:
if what >= 0:
if tmp2:
pss = int(tmp2.group(1))
pss_count[what] += pss
if tmp3:
pss = int(tmp3.group(1))
swapPss_count[what] += pss
# print("what:%d, pss:%d" % (what, pss))
if pss > 0:
pssSum_count[what] += pss
tmplist = type_list[what]
if name in tmplist:
tmplist[name] += pss
else:
tmplist[name] = pss
else:
tmp3 = match_head(line2)
if tmp3:
line = line2
prewhat = what
break
def print_result(args):
if args.pid and not args.output:
output = "%d_smaps_analysis.txt" % pid
else:
output = args.output
type = args.type
simple = args.simple
index = -1
if not type == "ALL":
if type in pss_type:
index = pss_type.index(type)
else:
print("Please enter a correct memory type")
return
output_file = open(output, 'w')
if index == -1:
for i,j,m,n,z in zip(pss_type, pssSum_count, pss_count, swapPss_count, type_list):
tmp = "%s : %.3f M" % (i, float(j)/1000)
print(tmp)
output_file.write(tmp)
output_file.write("\n")
tmp = "\tpss: %.3f M" % (float(m) / 1000)
print(tmp)
output_file.write(tmp)
output_file.write("\n")
tmp = "\tswapPss: %.3f M" % (float(n) / 1000)
print(tmp)
output_file.write(tmp)
output_file.write("\n")
if not simple:
count = Counter(z)
for j in count.most_common():
tmp = "\t\t%s : %d kB" % (j[0], j[1])
print(tmp)
output_file.write(tmp)
output_file.write("\n")
else:
tmp = "%s : %.3f M" % (pss_type[index], float(pssSum_count[index]) / 1000)
print(tmp)
output_file.write(tmp)
output_file.write("\n")
tmp = "\tpss: %.3f M" % (float(pss_count[index]) / 1000)
print(tmp)
output_file.write(tmp)
output_file.write("\n")
tmp = "\tswapPss: %.3f M" % (float(swapPss_count[index]) / 1000)
print(tmp)
output_file.write(tmp)
output_file.write("\n")
if not simple:
count = Counter(type_list[index])
for j in count.most_common():
tmp = "\t\t%s : %d kB" % (j[0], j[1])
print(tmp)
output_file.write(tmp)
output_file.write("\n")
if __name__ == "__main__":
args = help()
if args.filename:
if os.path.exists(args.filename):
parse_smaps(args.filename)
print_result(args)
else:
print("smaps is not exist")
elif args.pid:
if args.pid.isdigit():
pid = int(args.pid)
if pid > 0:
check_cmd = "adb shell su root ls /proc/%d/smaps >> /dev/null" % int(pid)
ret = os.system(check_cmd)
if ret == 0:
cmd = "adb shell su root cat /proc/%d/smaps" % int(pid)
smaps_filename = "%d_smaps_file.txt" % pid
ret = os.popen(cmd)
new_file = open(smaps_filename, 'w')
lines = ret.readlines()
for line in lines:
new_file.write(line)
parse_smaps(smaps_filename)
print_result(args)
else:
print("/proc/%d/smaps cannot be accessed" % pid)
else:
print("Please enter a correct pid")
else:
print("Please enter a correct pid")
else:
print("Please provide a pid or a smaps file")
参考网址:
http://light3moon.com/2020/12/07/Android%20%E5%86%85%E5%AD%98%E4%BC%98%E5%8C%96%E6%96%B9%E6%B3%95/
查看手机配置
可以通过这个命令查看heap的差异