内存拆解分析表:学习版[图片]

对拆解system中主要是对比测试机和对比机之间的差距,测试机那些地方高于对比机

拆解表,作为理解

在拆解表中system测试机比对比机多出113M

这说明是有问题的

对system拆解:

system12345
对比机9102294380941069391081628
测试机102520103318103183103160103458
差值1149889389077925021830
Java Heap12345
对比机13208415325617262010668092732
测试机15481618234412139214783298896
差值2273229088-51228411526164
Java Heap12345
对比机5813655360556925534854488
测试机4256842292443404410444464
差值-15568-13068-11352-11244-10024
Code12345
对比机6392864872640526439262700
测试机164736165088155324155492155540
差值100808100216912729110092840
Stack12345
对比机43484092409240923984
测试机34963500350435083508
差值-852-592-588-584-476
Graphics12345
对比机1662016620166201662016620
测试机19921992199219921992
差值-14628-14628-14628-14628-14628
Private Other12345
对比机1376813604136081362413772
测试机4192841936419524194441616
差值2816028332283442832027844

上面拆解表是模块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占用的内存会比较多,需要对这两块进行分析和拆解,拆解方式在下文详解

  1. 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 -htop查看内存消耗,发现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 -htop查看内存消耗,发现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)说明
total7.6可分配内存总计值
used7.26已分配内存
free0.17未分配内存
buff/cache0.17buff与缓存
  • 各属性满足公式:total = used + free + buff/cache

  • used在该linux版本(centos7)上,已经反映实际分配的内存,不需要再去除buff/cache部分

3.3 top进程列表内存相关列统计

合计(G)说明
VIRT14.236进程申请的虚拟内存大小,申请不意味着分配,该值与实际内存消耗关系不大。
RES1.9747进程常驻内存,包含进程间共享内存。
SHR0.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:252SwapCached包含的是被确定要swap-out,但是尚未写入交换区的匿名内存页。SwapCached内存页会同时被统计在LRU或AnonPages或Shmem中,它本身并不占用额外的内存。
Active:1182220active包含active anon和active fileLRU是一种内存页回收算法,Least Recently Used,最近最少使用。LRU认为,在最近时间段内被访问的数据在以后被再次访问的概率,要高于最近一直没被访问的页面。于是近期未被访问到的页面就成为了页面回收的第一选择。Linux kernel会记录每个页面的近期访问次数,然后设计了两种LRU list: active list 和 inactive list, 刚访问过的页面放进active list,长时间未访问过的页面放进inactive list,回收内存页时,直接找inactive list即可。另外,内核线程kswapd会周期性地把active list中符合条件的页面移到inactive list中。
Inactive:1213960inactive包含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:4157436swap空间总计
SwapFree:4157172当前剩余swap
Dirty:8需要写入磁盘的内存页的大小Dirty并不包括系统中全部的dirty pages,需要再加上另外两项:NFS_Unstable 和 Writeback,NFS_Unstable是发给NFS server但尚未写入硬盘的缓存页,Writeback是正准备回写硬盘的缓存页。
Writeback:0正在被写回的内存页的大小
AnonPages:1402140Anonymous pages(匿名页)数量 + AnonHugePages(透明大页)数量进程所占的内存页分为anonymous pages和file-backed pages,理论上,所有进程的PSS之和 = Mapped + AnonPages。PSS是Proportional Set Size,每个进程实际使用的物理内存(比例分配共享库占用的内存),可以在/proc/[1-9]*/smaps中查看。
Mapped:41584正被用户进程关联的file-backed pagesCached包含了所有file-backed pages,其中有些文件当前不在使用,但Cached仍然可能保留着它们的file-backed pages;而另一些文件正被用户进程关联,比如shared libraries、可执行程序的文件、mmap的文件等,这些文件的缓存页就称为mapped。
Shmem:8576Shmem统计的内容包括: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+SUnreclaimslab是linux内核的一种内存分配器。linux内核的动态内存分配有以下几种方式:1.alloc_pages/__get_free_page:以页为单位分配。2.vmalloc:以字节为单位分配虚拟地址连续的内存块。3.slab:对小对象进行分配,不用为每个小对象分配一个页,节省了空间;内核中一些小对象创建析构很频繁,Slab对这些小对象做缓存,可以重复利用一些相同的对象,减少内存分配次数。4.kmalloc:以slab为基础,以字节为单位分配物理地址连续的内存块。
SReclaimable:86720slab中可回收的部分。
SUnreclaim:56500slab中不可回收的部分。
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:7184Page Table的消耗的内存页Page Table的用途是翻译虚拟地址和物理地址,它是会动态变化的,要从MemTotal中消耗内存。
NFS_Unstable:0发给NFS server但尚未写入硬盘的缓存页
Bounce:0bounce buffering消耗的内存页有些老设备只能访问低端内存,比如16M以下的内存,当应用程序发出一个I/O 请求,DMA的目的地址却是高端内存时(比如在16M以上),内核将在低端内存中分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处。
WritebackTmp:0正准备回写硬盘的缓存页
CommitLimit:8154476overcommit阈值,CommitLimit = (Physical RAM * vm.overcommit_ratio / 100) + SwapLinux是允许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:1284096AnonHugePages统计的是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。

该值远小于使用freetop得到的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的差异

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/614846.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Python系列】字节串与字典字节串

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

模拟集成电路(3)----单级放大器(共源极)

模拟集成电路(3)----单级放大器&#xff08;共源极&#xff09; 放大是模拟电路的基本功能 大多数自然模拟信号太小而无法处理需要足够的信噪比 理想的放大器 线性&#xff1a;无限的幅度和频率范围 输入阻抗无限大 输出阻抗无限小 共源放大器 共源放大器就是将源极接A…

关于Matplotlib如何在网页中使用?

目录 一、如何在网页中使用matplotlib 二、如何使用mpld3在网页中显示图表 三、如何使用matplotlibflask在网页中显示图表 一、如何在网页中使用matplotlib Matplotlib是Python中一个非常流行的可视化库。然而&#xff0c;Matplotlib主要是为桌面应用程序设计的&#xff0c;…

初识java--javaSE(3)--方法,递归,数组,

文章目录 一 方法的使用1.1 什么是方法&#xff1f;main方法注意事项 1.2 方法的调用嵌套调用在方法调用时形参与实参的关系&#xff1a; 1.3 方法的重载方法重载的意义&#xff1f;总结方法重载&#xff1a;方法签名&#xff1a; 二 递归什么是递归&#xff1f;递归的精髓&…

BUU-[GXYCTF2019]Ping Ping Ping

考察点 命令执行 题目 解题 简单测试 ?ip应该是一个提示&#xff0c;那么就测试一下?ip127.0.0.1 http://0c02a46a-5ac2-45f5-99da-3d1b0b951307.node4.buuoj.cn:81/?ip127.0.0.1发现正常回显 列出文件 那么猜测一下可能会有命令执行漏洞&#xff0c;测试?ip127.0.…

Github图片显示不出来?两步解决!

很多同学可能和我一样&#xff0c;在GitHub中找一些项目或者资料的时候&#xff1b;总是会看到一些图片显示不出来&#xff0c;或者数学公式乱码&#xff1a; 比如这样 还有这样 其实这个主要是因为DNS污染导致的&#xff0c;具体大家可以百度&#xff0c;这边不详细介绍。 解决…

libcity笔记:

1 __init__ 2 encode 得到的内容如下&#xff1a; data_feature的内容&#xff1a; 一共有多少个location1【包括pad的一个】最长的时间间隔&#xff08;秒&#xff09;最长的距离间隔&#xff08;千米&#xff09;多少个useer idpadding 的locationidpad_item的内容 location…

ppt---C语言

注意某些符号和我们手写的不一样&#xff08;&#xff09;乘法&#xff0c;除法等

实现桌面动态壁纸——认识 WebView2 控件

目录 前言 一、什么是 WebView2 &#xff1f; 二、使用示例存储库 2.1 下载存储库 2.2 编译解决方案项目文件 2.3 运行示例程序 三、如何修改 WebView2 示例 本文来源于&#xff1a;https://blog.csdn.net/qq_59075481/article/details/138637909。 前言 上一节我们讲…

05-10 周五 FastBuild 容器启动引起超时问题定位与解决

05-10 周五 FastBuild 容器启动超时问题 时间版本修改人描述2024年5月11日16:45:33V0.1宋全恒新建文档2024年5月11日22:37:21V1.0宋全恒完成解决方案的撰写&#xff0c;包括问题分析&#xff0c;docker命令 简介 关于FastBuild的优化&#xff0c;已经撰写了多个博客&#xff0…

书生浦语训练营第四次课作业

基础作业 环境配置 拷贝internlm开发机内的环境 studio-conda xtuner0.1.17# 激活环境 conda activate xtuner0.1.17 # 进入家目录 &#xff08;~的意思是 “当前用户的home路径”&#xff09; cd ~ # 创建版本文件夹并进入&#xff0c;以跟随本教程 mkdir -p /root/xtuner0…

Celery Redis 集群版连接和PyCharm启动配置

目录 使用Redis cluster版作为broker原因 PyCharm配置 使用Redis cluster版作为broker 在celery5及其之前版本&#xff0c;需要配置如下才可行 celery_app.conf.update( broker_transport_options{“global_keyprefix”: “{celery}:”}, ) 原因 https://github.com/celery/…

【练习4】

1.两数之和 暴力&#xff1a; class Solution { public:vector<int> twoSum(vector<int>& nums, int target) {int n nums.size();vector<int> res(2, -1); // 初始化结果为-1for (int i 0; i < n; i) {int temp nums[i];for (int j i 1; j <…

若依修改表格滚动条宽度

找到目录ruoyi-ui/src/assets/styles/ruoyi.scss 添加滚动条样式 //修改滚动条的宽度和高度&#xff08;横向是高度&#xff0c;纵向是宽度&#xff09;::-webkit-scrollbar {width: 14px;height: 14px;background-color: transparent;}

配置Docker对象与管理守护进程

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 本章节的快速目录导航&#xff1a; 一、配置Docker对象 1.1、Docker对象的标记 1.2、格式化命令和日志的输出 二、示例&#xff1a; 2.1、管理…

大模型微调之 在亚马逊AWS上实战LlaMA案例(十)

大模型微调之 在亚马逊AWS上实战LlaMA案例&#xff08;十&#xff09; 训练数据集格式 SageMaker JumpStart 目前支持域适应格式和指令调整格式的数据集。在本节中&#xff0c;我们指定两种格式的示例数据集。有关更多详细信息&#xff0c;请参阅附录中的数据集格式化部分。 …

ECharts系列:基本使用及配置项

目录 基本使用 配置项的写法与位置 配置项option包含属性 各个配置项属性大全 基本使用 在阅读本篇文章时请参考ECharts官网地址中的内容配合了解 首先我们知道ECharts图表中有许多类型&#xff0c;如折线图、柱状图、饼形图等&#xff0c;下面我以折线图为例讲解ECharts图…

户籍管理系统的设计与实现

【免费】户籍管理系统的设计与实现.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89296790 ​​​​​​​户籍管理系统的设计与实现 摘 要 当今社会人们生活质量越来越高&#xff0c;人们对生活品质的追求不断提升&#xff0c;对于孩子求学&#xff0c;变更住…

【一站式学会Kotlin】第四节默认参数和具名参数、unit返回值类型

作者介绍&#xff1a; 百度资深Android工程师T6&#xff0c;在百度任职7年半。 目前&#xff1a;成立赵小灰代码工作室&#xff0c;欢迎大家找我交流Android、微信小程序、鸿蒙项目。文章底部&#xff0c;csdn有为我插入微信的联络方式&#xff0c;欢迎大家联络我。 一&#x…

C++笔试强训day18

目录 1.压缩字符串 2.chika和蜜柑 3.01背包 1.压缩字符串 链接 注意细节&#xff1a; 1.数量为一个时不用输出个数 2.当数量超过 9 时&#xff0c;需要逐个拿出 n 的位数&#xff0c;如153次&#xff0c;需要拿出1、5、3三个数 详细代码&#xff1a; class Solution { publ…