开机剩余内存拆解
对拆解system中主要是对比测试机和对比机之间的差距,测试机那些地方高于对比机。
在拆解表中system测试机比对比机多出113M
这说明是有问题的
对system拆解:
上面拆解表是模块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) = 28769Private 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中的各项指标,只是案例不要和自己项目中进行对比,但是公式是一样的,没有什么区别
开机剩余内存-详细拆解:
MemAvailable-拆解:
MemAvailable" 是从 /proc/meminfo 文件中计算出来的,这个文件提供了关于系统内存使用情况的详细信息。MemAvailable 是根据当前系统内存状态和可用内存相关指标进行计算得出的一个估算值。
具体地说,MemAvailable 的计算过程是基于以下几个关键指标:
- MemTotal:系统总内存量,即整个系统可用内存的总和。
- MemFree:未被任何进程占用的空闲内存量。
- Buffers:用于存储文件系统 metadata 的缓冲区占用的内存量。
- Cached:文件系统缓存占用的内存量。
- SReclaimable:可回收内存量,即文件系统缓存和页缓存中可以被回收的部分。
- Shmem:用于存储共享内存段的内存量。
- SwapCached:被缓存到交换空间的内存量。
MemAvailable 的计算公式大致如下:
MemAvailable=MemFree+Buffers+Cached+SReclaimable+Shmem−SwapCachedMemAvailable=MemFree+Buffers+Cached+SReclaimable+Shmem−SwapCached
这个值表示了当前系统中大约可以被立即分配给进程使用的内存量。在实践中,MemAvailable 提供了一个更准确的系统可用内存的估计,相比于仅考虑空闲内存和缓存内存的指标更为全面。
Cache进程-拆解:
Total PSS by OOM adjustment:下的Cached所以进程的公式为(PrivateDirty-TOTAL)+(SharedClean-TOTAL)-Code
其他拆解图:
swap总值:
Total PSS by OOM adjustment和Total PSS by category:下面全部的swap总和
详细教学:
https://www.jianshu.com/p/af22eb653fc3
内存拆解分析通用文档
内存分析通用文档
说明
该文档对常见的内存超标问题进行总结,并给出内存拆解方式
一. 初略分析
查看process整体内存状态的两种方式:
-
dumpsys meminfo
-
根据测试提供的dumpsys meminfo数据,可先粗略看一下内存状态,如下图
tab1是较为详细的内存分布,一般详细的拆解需要从tab1中查看;
tab4为Objects是统计App内部组件对象个数,其中Views、ViewRootImpl以及Activities个数,若这些项占用过多,考虑出现了内存泄露或是测试出现异常;
tab2则是对tab1的再统计,其它字段较易理解,Graphics和System的统计逻辑如下已给出。
tab1中每项的内存占用
对内存进行的一般依据tab1的数据,每一个小项的内存值为 Pss Total + SwapPss,如上图中,每一项对应的内存值如下:
(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部分内存可用以下方式,通常会需要与对比机进行对比,以确认多出的调用内存
2.Graphics内存拆解
一般情况下占用过大的是EGL和GL部分的内存
MTK平台
- EGL
一般情况下应用在后台时,EGL内存值为0,dumpsys meminfo 中不会包含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,统计逻辑出现修改,且存在单个进程内存统计不准确的情况,后续补充分析方法。
- 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//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的内存块是否占用过多
- 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//smaps .
python check_smaps.py smaps
可筛选smaps中的信息,smaps中只能显示出哪一块内存比较大,其他细项依然需要从其他角度定位。
5.EGL内存问题
现出现个别MTK平台EGL统计过高的情况,可以通过dump信息并对比其他mtk平台查看是否是统计问题,前台为执行case时,执行 adb shell dumpsys meminfo 查看EGL大小,后台内存为退至后台3min使用adb shell dumpsys meminfo 查看EGL大小,对比之前mtk机型或是高通平台机型,若EGL明显偏高可在jira下先说明EGL占用较高的情况。
学习拆解MEMINFO
一、背景
近期在公司的某台linux虚拟机上,发现内存几乎消耗殆尽,但找不到其去向。
在调查过程中,重点分析了/proc/meminfo文件,对其内存占用进行了学习与分析。
特记录在此,与君分享。
- 参考资料:http://linuxperf.com/?cat=7
二、环境 - 虚拟机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结果第四行内存总体使用情况
- 各属性满足公式:total = used + free + buff/cache
-used在该linux版本(centos7)上,已经反映实际分配的内存,不需要再去除buff/cache部分
3.3 top进程列表内存相关列统计
3.4 问题来了
RES合计值比used少了5G多!这些内存哪去了?
理论上,各进程的RES合计值因为会重复计算共享内存,应该比used值略大。实际上这两个值也往往是接近的,不应该差这么多。
四、清查linux内存消耗
为了进一步检查linux中内存消耗的去向,需要对/proc/meminfo文件进行一次彻底的分析统计。
linux上各种内存查看工具如free,top实际上都是从/proc下面找linux内核的各种统计文件。
4.1 /proc/meminfo内容分析
4.2 根据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
https://github.com/Gracker/Android-App-Memory-Analysis
参考网址:
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/
…