安卓开机剩余内存拆解



对拆解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 的计算过程是基于以下几个关键指标:

  1. MemTotal:系统总内存量,即整个系统可用内存的总和。
  2. MemFree:未被任何进程占用的空闲内存量。
  3. Buffers:用于存储文件系统 metadata 的缓冲区占用的内存量。
  4. Cached:文件系统缓存占用的内存量。
  5. SReclaimable:可回收内存量,即文件系统缓存和页缓存中可以被回收的部分。
  6. Shmem:用于存储共享内存段的内存量。
  7. 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整体内存状态的两种方式:

  1. dumpsys meminfo

  2. 根据测试提供的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占用的内存会比较多,需要对这两块进行分析和拆解,拆解方式在下文详解

  3. 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的内存块是否占用过多

  1. 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/

  • 9
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值