dumpsys meminfo 详解

Posted 私房菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了dumpsys meminfo 详解相关的知识,希望对你有一定的参考价值。

源码基于:Android R

0. 前言

其实,很久以前在 android 查看内存使用情况 一文中已经分析过dumpsys meminfo,但最近在统计内存数据的时候发现怎么也对不上,所以重新分析了下源码,之前在 android 查看内存使用情况 一文只是讲了个大概框架或含义。本篇博文会结合代码详细分析下AMS 下meminfo service 以及dump 的过程。

1. meminfo 的起点dumpsys

AMS 下的meminfo 统计是通过dumpsys 命令进行dump 的,这个是存放在 /system/bin/下的bin 文件。源码目录位于 frameworks/native/cmds/dumpsys/下,详细的请查看 android 中的dumpsys 一文。

2. meminfo services -- MemBinder

可以dump的这些service都是在ServiceManager里面添加上的,例如meminfo是在:frameworks/base/services/java/com/android/server/am/ActivityManagerService.java的函数setSystemProcess添加的:

frameworks/base/services/core/java/com/android/server/am/AMS.java


public void setSystemProcess() 
        try 
            ...

            ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
                    DUMP_FLAG_PRIORITY_HIGH);

            ...

 注意:

 通过 android 中的dumpsys 一文的step7,我们得知,如果某service(例如,meminfo) 需要通过dumpsys 命令进行dump 操作,必要将指定dump flag。这里meminfo 在addService 的时候指定了dump flag 为 DUMP_FLAG_PRIORITY_HIGH

下面来看下:

frameworks/base/services/core/java/com/android/server/am/AMS.java

   static class MemBinder extends Binder 
        ActivityManagerService mActivityManagerService;
        private final PriorityDump.PriorityDumper mPriorityDumper =
                new PriorityDump.PriorityDumper() 
            @Override
            public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args,
                    boolean asProto) 
                dump(fd, pw, new String[] "-a", asProto);
            

            @Override
            public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) 
                mActivityManagerService.dumpApplicationMemoryUsage(
                        fd, pw, "  ", args, false, null, asProto);
            
        ;

        MemBinder(ActivityManagerService activityManagerService) 
            mActivityManagerService = activityManagerService;
        

        @Override
        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) 
            try 
                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(false);

                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                        "meminfo", pw)) return;
                PriorityDump.dump(mPriorityDumper, fd, pw, args);
             finally 
                mActivityManagerService.mOomAdjuster.mCachedAppOptimizer.enableFreezer(true);
            
        
    

里面定义了两个成员变量:

  • mActivityManagerService:记录AMS 对象;
  • mPriorityDumper:通过其dump() 接口,连接AMS.dumpApplicationMemoryUsage();

真正实现的地方是在AMS 的dumpApplicationMemoryUsage() 中。

3. dumpsys meminfo -h

进入dumpApplicationMemoryUsage() 之后,会看到一个while 循环用来分析dumpsys meminfo 附带的参数,这里首先来看下 -h,通过此参数进一步了解 dumpsys meminfo 的其他参数选项。

代码这里就不贴了,直接来看下终端的输出:

meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]
  -a: include all available information for each process.
  -d: include dalvik details.
  -c: dump in a compact machine-parseable representation.
  -s: dump only summary of application memory usage.
  -S: dump also SwapPss.
  --oom: only show processes organized by oom adj.
  --local: only collect details locally, don't call process.
  --package: interpret process arg as package, dumping all
             processes that have loaded that package.
  --checkin: dump data for a checkin
  --proto: dump data to proto
If [process] is specified it can be the name or
pid of a specific process to dump.
  • -a:包含所有可用信息,更确切它包含了-d、-s、-S;
  • -d:需要包含dalvik 详细信息;
  • -c:dump 成一个压缩的机器解析的表达形式;
  • -s:只dump 应用内存的概要;
  • -S:将swap pss 信息也dump 出来;
  • --oom:只dump RSS/PSS 的OOM adj 信息;
  • --local:只在本地收集信息,只有在-a 或 -s 指定的时候才生效;
  • --package:dump 所有加载指定package 的进程;
  • --checkin:将数据dump 下来checkin;
  • --proto:dump proto的数据;
  • dumpsys meminfo 可以跟 [process],此process 可以是进程名,也可以是进程pid;

4. collectProcesses()

当dumpsys meminfo 的命令行参数解析完之后,会调用collectProcesses() 来确认符合命令的packages 或pid:

frameworks/base/services/core/java/com/android/server/am/ProcessList.java

   ArrayList<ProcessRecord> collectProcessesLocked(int start, boolean allPkgs, String[] args) 
        ArrayList<ProcessRecord> procs;
        if (args != null && args.length > start
                && args[start].charAt(0) != '-') 
            procs = new ArrayList<ProcessRecord>();
            int pid = -1;
            try 
                pid = Integer.parseInt(args[start]);
             catch (NumberFormatException e) 
            
            for (int i = mLruProcesses.size() - 1; i >= 0; i--) 
                ProcessRecord proc = mLruProcesses.get(i);
                if (proc.pid > 0 && proc.pid == pid) 
                    procs.add(proc);
                 else if (allPkgs && proc.pkgList != null
                        && proc.pkgList.containsKey(args[start])) 
                    procs.add(proc);
                 else if (proc.processName.equals(args[start])) 
                    procs.add(proc);
                
            
            if (procs.size() <= 0) 
                return null;
            
         else 
            procs = new ArrayList<ProcessRecord>(mLruProcesses);
        
        return procs;
    

代码还是比较清晰的:

  • 如果命令行指定了pid,那么就收集这些进程;
  • 如果设定了packageName,就收集这些package;
  • 如果没有设定,则收集所有的 LRU process;

注意,前两点有可能收集的 proc 为空,因为设定的参数有可能是假的或者无法匹配。

这个时候终端上还提示No process:

shift:/ # dumpsys meminfo 12345
No process found for: 12345

5. dumpApplicationMemoryUsage()

这个函数的代码量太大,这里抽重点的地方剖析下。

5.1 几个变量thread、pid、oomadj、mi

frameworks/base/services/core/java/com/android/server/am/AMS.java

    private final void dumpApplicationMemoryUsage() 

        ...

        for (int i = numProcs - 1; i >= 0; i--) 
            final ProcessRecord r = procs.get(i);
            final IApplicationThread thread;
            final int pid;
            final int oomAdj;
            final boolean hasActivities;
            synchronized (this) 
                thread = r.thread;
                pid = r.pid;
                oomAdj = r.getSetAdjWithServices();
                hasActivities = r.hasActivities();
            
            if (thread != null) 
                if (mi == null) 
                    mi = new Debug.MemoryInfo();
                

在最开始的时候需要注意几个重点的初始化:

  • thread:指定IApplicationThread,当 --local 没有设定时(大部分不会设定),需要通过该thread  读取应用端数据,并通过pipe 方式传递给AMS
  • oomAdj:dumpsys meminfo 中重要的一组数据,针对LMKD,详细查看 adj score的算法剖析 一文;
  • pid:当前应用的pid,dumpsys meminfo 的时候会伴随显示;
  • mi:内存数据的核心,下面会详细讲解这个变量,详细见第 5.2 节;

5.2 Debug.MemoryInfo()

源码目录:frameworks/base/core/java/android/os/Debug.java

这里提供了很多的native 接口:

    public static native long getNativeHeapSize();
    public static native long getNativeHeapAllocatedSize();
    public static native long getNativeHeapFreeSize();
    public static native void getMemoryInfo(MemoryInfo memoryInfo);
    public static native boolean getMemoryInfo(int pid, MemoryInfo memoryInfo);
    public static native long getPss();
    public static native long getPss(int pid, long[] outUssSwapPssRss, long[] outMemtrack);

5.3 Debug.getMemoryInfo()

接着第 5.1 节的代码继续往下看:

                if (opts.dumpDetails || (!brief && !opts.oomOnly)) 
                    reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
                    startTime = SystemClock.currentThreadTimeMillis();
                    if (!Debug.getMemoryInfo(pid, mi)) 
                        continue;
                    
                    endTime = SystemClock.currentThreadTimeMillis();
                    hasSwapPss = mi.hasSwappedOutPss;
                 else 
                    reportType = ProcessStats.ADD_PSS_EXTERNAL;
                    startTime = SystemClock.currentThreadTimeMillis();
                    long pss = Debug.getPss(pid, tmpLong, null);
                    if (pss == 0) 
                        continue;
                    
                    mi.dalvikPss = (int) pss;
                    endTime = SystemClock.currentThreadTimeMillis();
                    mi.dalvikPrivateDirty = (int) tmpLong[0];
                    mi.dalvikRss = (int) tmpLong[2];
                
  • opts.dumpDetails 主要用以获取应用的详细信息。一般在指定pid 或者 package的时候,或者直接指定命令参数 -s 的时候该变量被置为 true;
  • brief 默认值为false,从MemBinder 传递过来;
  • opts.oomOnly 只有在 --oom 指定时被置为 true;

在没有指定 --oom 时,代码都是会走 if 的case,会通过Debug.getMemoryInfo() 获取进程的详细内存信息:

frameworks/base/core/jni/android_os_Debug.cpp

static jboolean android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
        jint pid, jobject object)

    bool foundSwapPss;
    stats_t stats[_NUM_HEAP];
    memset(&stats, 0, sizeof(stats));

    if (!load_maps(pid, stats, &foundSwapPss)) 
        return JNI_FALSE;
    

    struct graphics_memory_pss graphics_mem;
    if (read_memtrack_memory(pid, &graphics_mem) == 0) 
        stats[HEAP_GRAPHICS].pss = graphics_mem.graphics;
        ...
    

    for (int i=_NUM_CORE_HEAP; i<_NUM_EXCLUSIVE_HEAP; i++) 
        stats[HEAP_UNKNOWN].pss += stats[i].pss;
        ...
    

    for (int i=0; i<_NUM_CORE_HEAP; i++) 
        env->SetIntField(object, stat_fields[i].pss_field, stats[i].pss);
        ...
    

    env->SetBooleanField(object, hasSwappedOutPss_field, foundSwapPss);

    ...

    for (int i=_NUM_CORE_HEAP; i<_NUM_HEAP; i++) 
        ...
    

    env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);
    return JNI_TRUE;

  • 通过 load_maps() 从 /proc/pid/smaps 中获取进程的内存信息;
  • 通过read_memtrack_memory() 通过libmemtrack.so 获取 graphics 的内存信息;
  • 通过 env->SetIntField() 将 UNKNOWN、dalvik、heap 所属组的信息设置到 Meminfo 中;
  • 通过otherArray 将其他信息设置到 MemoryInfo.otherStats[] 中;

5.3.1 load_maps()

static bool load_maps(int pid, stats_t* stats, bool* foundSwapPss)

    *foundSwapPss = false;
    uint64_t prev_end = 0;
    int prev_heap = HEAP_UNKNOWN;

    std::string smaps_path = base::StringPrintf("/proc/%d/smaps", pid);
    auto vma_scan = [&](const meminfo::Vma& vma) 
        ...
    ;

    return meminfo::ForEachVmaFromFile(smaps_path, vma_scan);

meminfo::ForEachVmaFromFile() 是 libmeminfo.so 接口,代码位于 /system/memory/libmeminfo/下。

这里总结一个调用框架图:

主要是读取进程 smaps 节点,根据属性的不同,进程的虚拟地址空间会被划分成若干个 VMA,每个VMA 通过vm_next 和vm_prev 组成双向链表,链表位于进程的 task_struct->mm_struct->mmap 中。当通过proc接口读取进程的smaps文件时,内核会首先找到该进程的vma链表头,遍历链表中的每一个vma, 通过walk_page_vma统计这块vma的使用情况,最后显示出来。

下面是其中一块 VMA 的统计信息:

  • Rss:常驻物理内存,是进程在RAM 中实际持有的物理内存。Rss 中包含了共享库占用的内存,所以可能产生误导;
  • Pss:比例使用的物理内存,Pss 与Rss 区别是Pss 统计了共享库是平均占用内存。若一个so 占用内存30kb,这个so 被3 个进程共享,那么某一个进程Pss 统计时该so 占用的内存为 30/3 kb;
  • Private_Dirty:私有的内存数据,只不过是Dirty的,与Private_Clean 相反。Dirty 包括了page_dirty 和pte_dirty,page_dirty 就是所说的脏页(文件读到内存中被修改过,就会标记为脏页)。pte_dirty 则当 vma 用以anonymous 的时候,读写这段 vma 触发page fault 时调用do_anonymous_page,如果vma_flags 中包含 VM_WRITE,则会通过pte_mkdirty(entry) 标记;
  • Private_Clean:私有干净数据,与Private_Dirty 相反;
  • Shared_Dirty:同Private_Dirty,该内存至少有一个其他进程在引用,而且至少有一个进程在修改;
  • Shared_Clean:同Private_Clean,与Shared_Dirty 相反;
  • Swap:被交换的native 页,可称为SwapRss;
  • SwapPss:被交换的native 页,按照比例统计;

一般情况下,在Android 中就是ZRAM,通过压缩内存页面并将其放入动态分配的内存交换区来增加系统中的可用内存量,压缩的都是匿名页。

但下面这段vma 是文件映射的,但还有swap字段的,这是因为这个文件是通过mmap到进程地址空间的。当标记中有MAP_PRIVATE时,这表示是一个copy-on-write的映射,虽然是file-backed,但当向这个数据写入数据的时候,会把数据拷贝到匿名页里,所以看到上面的 Anonymous: 也不为0。

5.3.2 smaps 中vma 与 HEAP 枚举对照

[heap]

[anon:libc_malloc]

[anon:scudo:

[anon:GWP-ASan

HEAP_NATIVE

[anon:dalvik-*

HEAP_DALVIK_OTHER
*.soHEAP_SO
*.jarHEAP_JAR
*.apkHEAP_APK
*.ttfHEAP_TTF

*.odex (*.dex)

HEAP_DEX

----HEAP_DEX_APP_DEX

*.vdex (@boot /boot  /apex)

HEAP_DEX

----HEAP_DEX_BOOT_VDEX

----HEAP_DEX_APP_VDEX

*.oatHEAP_OAT

*.art

*.art]  (@boot /boot  /apex)

HEAP_ART

----HEAP_ART_BOOT

----HEAP_ART_APP

/dev/HEAP_UNKNOWN_DEV
/dev/kgsl-3d0HEAP_GL_DEV
/dev/ashmem/CursorWindowHEAP_CURSOR

/dev/ashmem/jit-zygote-cache

/memfd:jit-cache

/memfd:jit-zygote-cache

HEAP_DALVIK_OTHER
/dev/ashmemHEAP_ASHMEM

详细的which_heap 和sub_heap 看load_maps() 函数。

5.3.3 read_memtrack_memory()

通过本函数,获取进程在GPU 上的分配内存。

下面是调用框架图:

 上图读取进程在GPU上分配的内存,每个厂商统计的策略可能不一样,这也是出现HAL层的原因。


5.4 thread.dumpMemInfo()

在第 5.1 节中列举了几个重要的变量,其中一个就是 IApplicationThread,每个进程独有。

当然这个case 是当 opts.dumpDetails 为true,也就是针对单个应用的meminfo;

       try 
            TransferPipe tp = new TransferPipe();
            try 
                thread.dumpMemInfo(tp.getWriteFd(),
                        mi, opts.isCheckinRequest, opts.dumpFullDetails,
                        opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs);
                tp.go(fd, opts.dumpUnreachable ? 30000 : 5000);
             finally 
                tp.kill();
            

创建TransferPipe 对象,里面会创建一个thread 和 一个pipe。然后将writeFd 给app 进程的ActivithThread,在ActivityThread 将解析app 进程的内存使用。当调用go() 函数时会启动TransferPipe 创建的thread,并等待pipe 通信,如果AMS 通过 readFd 获取到数据后,会通知 dumpsys 进程表示dump 完成。

下面来看下dumpMemInfo():

 ---->

frameworks/base/core/java/android/app/ActivityThread.java

        public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
                boolean dumpUnreachable, String[] args) 
            FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
            PrintWriter pw = new FastPrintWriter(fout);
            try 
                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
             finally 
                pw.flush();
                IoUtils.closeQuietly(pfd);
            
        

详细的代码这里不继续分析了,总结一个调用框架图:

dumpMeminfo() 中会通过getRuntime() 获取app 进程dalvik 的totalMemory 和freeMemory,并计算出 dalvikAllocated,得到app 进程虚拟机内存使用情况。

并且通过Debug.getNativeHeapSize() 等三个native 接口统计app 进程的native 使用。这里同上面第 5.3 节,依然会进入androi_os_Debug.cpp 中:

frameworks/base/core/jni/android_os_Debug.cpp

static jlong android_os_Debug_getNativeHeapSize(JNIEnv *env, jobject clazz)

    struct mallinfo info = mallinfo();
    return (jlong) info.usmblks;


static jlong android_os_Debug_getNativeHeapAllocatedSize(JNIEnv *env, jobject clazz)

    struct mallinfo info = mallinfo();
    return (jlong) info.uordblks;


static jlong android_os_Debug_getNativeHeapFreeSize(JNIEnv *env, jobject clazz)

    struct mallinfo info = mallinfo();
    return (jlong) info.fordblks;

mallinfo() 返回内存分配的统计信息,函数声明在 android/bionic/libc/include/malloc.h,函数定义在 android/bionic/libc/bionic/malloc_common.cpp:

extern "C" struct mallinfo mallinfo() 
  auto dispatch_table = GetDispatchTable();
  if (__predict_false(dispatch_table != nullptr)) 
    return dispatch_table->mallinfo();
  
  return Malloc(mallinfo)();

mallinfo() 主要是返回一个结构体,结构体中包含了通过malloc() 和其相关的函数调用所申请的内存信息,下面是结构体mallinfo 的具体信息(其他信息见man):

   struct mallinfo 
       int arena;     /* Non-mmapped space allocated (bytes) */
       int ordblks;   /* Number of free chunks */
       int smblks;    /* Number of free fastbin blocks */
       int hblks;     /* Number of mmapped regions */
       int hblkhd;    /* Space allocated in mmapped regions (bytes) */
       int usmblks;   /* Maximum total allocated space (bytes) */
       int fsmblks;   /* Space in freed fastbin blocks (bytes) */
       int uordblks;  /* Total allocated space (bytes) */
       int fordblks;  /* Total free space (bytes) */
       int keepcost;  /* Top-most, releasable space (bytes) */
   ;

我们这里返回的三个信息:

  • usmblks:alloc 的最大总内存(单位:字节);
  • uordblks:总的alloc 的内存(单位:字节);
  • fordblks:总的free 的内存(单位:字节);

 这两个输在如下图所示: 

5.5 MemInfoReader

在将MemInfoReader 之前,需要回到代码最开始,了解下变量 collectNative:

final boolean collectNative = !opts.isCheckinRequest && numProcs > 1 && !opts.packages;

当dump 多个应用或者所有应用的meminfo 时,变量 collectNative 被置true;如果只是打印单个应用的meminfo 时该值为 false,所以最终不会打印。

    MemInfoReader memInfo = new MemInfoReader();
    memInfo.readMemInfo();

详细的代码不再剖析,这里总结一个调用框架:

输出如下图:

6. meminfo 数据分析

6.1 单个应用的meminfo

shift:/ # dumpsys meminfo com.android.launcher3
Applications Memory Usage (in Kilobytes):
Uptime: 4443605 Realtime: 4443605

** MEMINFO in pid 1834 [com.android.launcher3] **
                   Pss  Private  Private     Swap      Rss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty    Total     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------   ------
  Native Heap    13647    13548        0        0    16152    21672    16064     1962
  Dalvik Heap     5338     5144        0        0     9496     6657     3329     3328
 Dalvik Other     1317     1192        0        0     2376
        Stack      636      636        0        0      644
       Ashmem        2        0        0        0       20
    Other dev      140        0      140        0      392
     .so mmap     7350      232       12        0    57116
    .jar mmap     2359        0       76        0    24984
    .apk mmap    22108        0    15208        0    51748
    .ttf mmap      110        0        0        0      376
    .dex mmap      348        8      336        0      432
    .oat mmap     1384        0        0        0    14560
    .art mmap     2434     1608        4        0    20496
   Other mmap      495       60       24        0     2896
      Unknown      599      588        0        0     1128
        TOTAL    58267    23016    15800        0    58267    28329    19393     5290

 App Summary
                       Pss(KB)                        Rss(KB)
                        ------                         ------
           Java Heap:     6756                          29992
         Native Heap:    13548                          16152
                Code:    15872                         149484
               Stack:      636                            644
            Graphics:        0                              0
       Private Other:     2004
              System:    19451
             Unknown:                                    6544

           TOTAL PSS:    58267            TOTAL RSS:   202816      TOTAL SWAP (KB):        0

 Objects
               Views:       89         ViewRootImpl:        1
         AppContexts:       10           Activities:        1
              Assets:       14        AssetManagers:        0
       Local Binders:       34        Proxy Binders:       43
       Parcel memory:       13         Parcel count:       69
    Death Recipients:        1      OpenSSL Sockets:        0
            WebViews:        0

 SQL
         MEMORY_USED:      682
  PAGECACHE_OVERFLOW:      292          MALLOC_SIZE:      117

 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       16             20         1/14/1  /data/user/0/com.android.launcher3/databases/widgetpreviews.db
         4      252             71        99/18/5  /data/user/0/com.android.launcher3/databases/app_icons.db
         4       16             57         6/17/4  /data/user/0/com.android.launcher3/databases/launcher.db

来看下其中几个主要概念:

           Java Heap:    24312  dalvik heap + .art mmap
         Native Heap:    62256
                Code:    66452    .so mmap + .jar mmap + .apk mmap + .ttf mmap + .dex mmap + .oat mmap
               Stack:       84
            Graphics:     5338    Gfx dev + EGL mtrack + GL mtrack
       Private Other:     9604  TotalPrivateClean + TotalPrivateDirty - java - native - code - stack - graphics
              System:    12900    TotalPss - TotalPrivateClean - TotalPrivateDirty

https://developer.android.com/studio/profile/investigate-ram?hl=zh-cn

  • Dalvik Heap

应用中 Dalvik 分配占用的 RAM。Pss Total 包括所有 Zygote 分配(如上述 PSS 定义所述,通过进程之间的共享内存量来衡量)。Private Dirty 数值是仅分配到您应用的堆的实际 RAM,由您自己的分配和任何 Zygote 分配页组成,这些分配页自从 Zygote 派生应用进程以来已被修改。

  • Heap Alloc

是 Dalvik 和原生堆分配器为您的应用跟踪的内存量。此值大于 Pss Total 和 Private Dirty,因为您的进程从 Zygote 派生,且包含您的进程与所有其他进程共享的分配。

  • .so mmap 和 .dex mmap

映射的 .so(原生)和 .dex(Dalvik 或 ART)代码占用的 RAM。Pss Total 数值包括应用之间共享的平台代码;Private Clean 是您的应用自己的代码。通常情况下,实际映射的内存更大 - 此处的 RAM 仅为应用执行的代码当前所需的 RAM。不过,.so mmap 具有较大的私有脏 RAM,因为在加载到其最终地址时对原生代码进行了修改。

  • .oat mmap

这是代码映像占用的 RAM 量,根据多个应用通常使用的预加载类计算。此映像在所有应用之间共享,不受特定应用影响。

  • .art mmap

这是堆映像占用的 RAM 量,根据多个应用通常使用的预加载类计算。此映像在所有应用之间共享,不受特定应用影响。尽管 ART 映像包含 Object 实例,它仍然不会计入您的堆大小。

But as to what the difference is between "Pss", "PrivateDirty", and "SharedDirty"... well now the fun begins.

A lot of memory in Android (and Linux systems in general) is actually shared across multiple processes. 
So how much memory a processes uses is really not clear. Add on top of that paging out to disk (let alone swap which we don't use on 
Android) and it is even less clear.

Thus if you were to take all of the physical RAM actually mapped in to each process, and add up all of the processes, 
you would probably end up with a number much greater than the actual total RAM.

The Pss number is a metric the kernel computes that takes into account memory sharing -- basically each page of RAM in a process is 
scaled by a ratio of the number of other processes also using that page. This way you can (in theory) add up the pss across all 
processes to see the total RAM they are using, and compare pss between processes to get a rough idea of their relative weight.

The other interesting metric here is PrivateDirty, which is basically the amount of RAM inside the process that can not be paged 
to disk (it is not backed by the same data on disk), and is not shared with any other processes. Another way to look at this is the 
RAM that will become available to the system when that process goes away (and probably quickly subsumed into caches and other uses 
of it).

从这篇文章中得知:

一般的android和linux系统中很多进程会共享一些mem,所以一个进程用到的mem其实不是十分清楚,因此如果将每个进程实际占用的mem加到一起,可能会发现这个结果会远远的超过实际的总的mem。

Pss 是kernel根据共享mem计算得到的值,Pss的值是一块共享mem中一定比例的值。这样,将所有进程Pss加起来就是总的RAM值了,也可以通过进程间Pss值得到这些进程使用比重情况。

PrivateDirty,它基本上是进程内不能被分页到磁盘的内存,也不和其他进程共享。查看进程的内存用量的另一个途径,就是当进程结束时刻,系统可用内存的变化情况(也可能会很快并入高速缓冲或其他使用该内存区的进程)。

 其他类型              smap 路径名称                       描述
 
Ashmem              /dev/ashmem            匿名共享内存用来提供共享内存通过分配一个多个进程
                                                         可以共享的带名称的内存块
Other dev            /dev/                         内部driver占用的在 “Other dev”                                                 
.so mmap             .so                           C 库代码占用的内存
.jar mmap            .jar                          Java 文件代码占用的内存
.apk mmap            .apk                          apk代码占用的内存
.ttf mmap            .ttf                          ttf 文件代码占用的内存
.dex mmap            .dex                          Dex 文件代码占用的内存
Other mmap                                         其他文件占用的内存 

6.2 系统meminfo 统计

Total RAM: 3,768,168K (status normal)
 Free RAM: 2,186,985K (   64,861K cached pss +   735,736K cached kernel + 1,386,388K free)
      ION:    58,408K (   53,316K mapped +      -128K unmapped +     5,220K pools)
 Used RAM: 1,440,349K (1,116,469K used pss +   323,880K kernel)
 Lost RAM:   140,822K
     ZRAM:        12K physical used for         0K in swap (2,097,148K total swap)
   Tuning: 256 (large 512), oom   640,000K, restore limit   213,333K (high-end-gfx)

6.2.1 Total RAM

pw.print(stringifyKBSize(memInfo.getTotalSizeKb()));

Total RAM 就是/proc/meminfo 中的 MemTotal

6.2.2 Free RAM

pw.print(stringifyKBSize(cachedPss + memInfo.getCachedSizeKb()
                                       + memInfo.getFreeSizeKb()));

 括号中的:

  • cached pss:All pss of process oom_score_adj >= 900
  • cached kernel:/proc/meminfo.Buffers + /proc/meminfo.KReclaimable +                                                                                         /proc/meminfo.Cached - /proc/meminfo.Mapped
  • free:/proc/meminfo.MemFree

6.2.3 ION

  • ion heap size: 读取 /sys/kernel/ion/total_heaps_kb
  • ion pool size:读取 /sys/kernel/ion/total_pools_kb
  • ion mapped + ion unmapped = ion heap

6.2.4 Used RAM

pw.print(stringifyKBSize(totalPss - cachedPss + kernelUsed)); 

 括号中的

  • used pss:totalPss - cachedPss
  • KernelUsed:/proc/meminfo.Shmem + /proc/meminfo.SlabUnreclaim + VmallocUsed + /proc/meminfo.PageTables + /proc/meminfo.KernelStack + [ionHeap]

注意上面的 KernelStack,只要在kernel 没有配置 CONFIG_VMAP_STACK 时,才会加上。

frameworks/base/core/java/com/android/internal/util/MemInfoReader.java

    public long getKernelUsedSizeKb() 
        long size = mInfos[Debug.MEMINFO_SHMEM] + mInfos[Debug.MEMINFO_SLAB_UNRECLAIMABLE]
                + mInfos[Debug.MEMINFO_VM_ALLOC_USED] + mInfos[Debug.MEMINFO_PAGE_TABLES];
        if (!Debug.isVmapStack()) 
            size += mInfos[Debug.MEMINFO_KERNEL_STACK];
        
        return size;
    

 6.2.5 Lost RAM

            final long lostRAM = memInfo.getTotalSizeKb() - (totalPss - totalSwapPss)
                    - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb()
                    - kernelUsed - memInfo.getZramTotalSizeKb();

 Lost RAM = proc/meminfo.Memtotal - (totalPss - totalSwapPss) - /proc/meminfo.Memfree - /proc/meminfo.Cached - kernel used - zram used

注意:

从这里大致可以推算出

FREE RAM + USed RAM - totalSwapPss + Lost RAM + ZRAM = Total RAM

6.2.6 ZRAM

  • zram physical size:/sys/block/zram0/mm_stat 第三个参数
  • swap total:proc/meminfo.SwapTotal
  • swap free:proc/meminfo.SwapFree
  • swap used: swap total - swap free

6.2.7 Tuning

        pw.print("   Tuning: ");
        pw.print(ActivityManager.staticGetMemoryClass());
        pw.print(" (large ");
        pw.print(ActivityManager.staticGetLargeMemoryClass());
        pw.print("), oom ");
        pw.print(stringifySize(
                    mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ), 1024));
        pw.print(", restore limit ");
        pw.print(stringifyKBSize(mProcessList.getCachedRestoreThresholdKb()));
        if (ActivityManager.isLowRamDeviceStatic()) 
            pw.print(" (low-ram)");
        
        if (ActivityManager.isHighEndGfx()) 
            pw.print(" (high-end-gfx)");
        
        pw.println();

large:dalvik.vm.heapsize属性取值,单位为MB

oom:ProcessList中mOomMinFree数组最后一个元素取值

restore limit:ProcessList中mCachedRestoreLevel变量取值,将一个进程从cached 到background的最大值,一般是mOomMinFree 最后一个值的 1/3.

low-ram:是否为low ram 设备,一般通过prop ro.config.low_ram,或者当ro.debuggable 使能时prop debug.force_low_ram。

high-end-gfx:ro.config.low_ram 为false,且ro.config.avoid_gfx_accel 为false,且config config_avoidGfxAccel 的值为false。

参考:https://blog.csdn.net/feelabclihu/article/details/105534175

Android 内存优化dumpsys meminfo PID 查看单进程内存信息详解

1.内存指标

Item全称含义等价
USSUnique Set Size物理内存进程独占的内存
PSSProportional Set Size物理内存Pss =Uss+按比例包含共享库
RSSResient Set Size物理内存RSS=USS+包含共享库
VSSVirtual Set Size虚拟内存VSS = RSS+未分配实际物理内存

内存的大小关系:VSS>RSS>=PSS>=USS
在实际分析中,一般是以PSS的内存为准,且也是最符合实际情况的统计值

2.使用

可以使用以下命令记录应用内存在不类型的RAM分配之间的划分情况

adb shell dumpsys meminfo package_name|pid [-d]

-d 标记会输出更多与Dalvik和ART内存占用情况相关的信息。
输出列出了应用当前实时的优点分配信息,以千字节(K)为单位。

  • 获取全局的内存信息
    除了通过上面的方式获取单个进程的内存信息,还可以获取系统整体内存情况。主要场景是用于确认单应用多进程的场景,对比下其他进程的当前内存状态,以及进程状态,我们要确保进程处理合理的状态。(比如UI进程退到后台要及时退到Cache状态,处于其他状态则要进行优化)
Total PSS by OOM adjustment:
    683,512K: Native
            ...
            854K: dumpsys (pid 19280)
            851K: lmkd (pid 789)
            843K: android.hardware.memtrack@1.0-service (pid 748)
            ...
    396,026K: System
        396,026K: system (pid 1597)
    666,050K: Persistent
        ...
        253,989K: com.android.systemui (pid 2326)
         46,482K: com.huawei.systemserver (pid 2588)
         40,122K: com.huawei.hiview (pid 2543)
         ...
     47,613K: Persistent Service
         34,164K: com.android.bluetooth (pid 20268)
    475,283K: Foreground
        242,908K: com.huawei.android.launcher (pid 10760 / activities)
    351,023K: Visible
        127,248K: com.huawei.hwid.core (pid 28223)
         ...
    117,719K: Perceptible
         95,123K: com.baidu.input_huawei (pid 6331)
         ...
    150,336K: A Services
         82,429K: com.huawei.health:DaemonService (pid 16676)
    196,753K: Previous
        159,932K: com.android.mms (pid 16921 / activities)
        ...
    527,396K: B Services
        164,049K: com.tencent.mm (pid 15990)
        ...
  1,177,708K: Cached
        139,752K: com.huawei.health (pid 18841)
        ...

3.各个属性的详解

3.1 实际样例

以微信举例,如下是一个完整的微信进程的meminfo信息

>adb shell "dumpsys meminfo com.tencent.mm -d"
Applications Memory Usage (in Kilobytes):
Uptime: 539705041 Realtime: 772532637

** MEMINFO in pid 15990 [com.tencent.mm] **
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap     7697     7688        0    38401    69632    56969    12662
  Dalvik Heap     7223     7220        0     5540    12964     9723     3241
 Dalvik Other     2277     2276        0     4570
        Stack       36       36        0       20
       Ashmem        2        0        0        0
    Other dev       21        0       20        0
     .so mmap    41972     4032    25560       35
    .jar mmap     1245        0        0        0
    .apk mmap    51193        0    13360        0
    .ttf mmap      681        0        0        0
    .dex mmap     5332       28      704      352
    .oat mmap      510        0        0        0
    .art mmap    16366    15940        0    11369
   Other mmap     3672      512      464        0
    GL mtrack     2272     2272        0        0
      Unknown     1848     1848        0     3731
        TOTAL   206365    41852    40108    64018    82596    66692    15903

 App Summary
                       Pss(KB)
                        ------
           Java Heap:    23160
         Native Heap:     7688
                Code:    43684
               Stack:       36
            Graphics:     2272
       Private Other:     5120
              System:   124405

               TOTAL:   206365       TOTAL SWAP PSS:    64018

 Objects
               Views:        0         ViewRootImpl:        0
         AppContexts:        5           Activities:        0
              Assets:        8        AssetManagers:        0
       Local Binders:       77        Proxy Binders:       84
       Parcel memory:       34         Parcel count:      142
    Death Recipients:        8      OpenSSL Sockets:        0
            WebViews:        0

 SQL
         MEMORY_USED:      376
  PAGECACHE_OVERFLOW:       57          MALLOC_SIZE:      117

 DATABASES
      pgsz     dbsz   Lookaside(b)          cache  Dbname
         4       28             35         2/18/3  /data/user/0/com.tencent.mm/databases/Scheduler.db
         4      108            109       26/30/15  /data/user/0/com.tencent.mm/databases/google_app_measurement.db

 Asset Allocations
    : 8000K

3.2数据来源

3.2.1 dumpsys 实现逻辑简单介绍

dumpsys的源码结构其实很简单,只有一个dumpsys.cpp
源码路径是:/frameworks/native/cmds/dumpsys/dumpsys.cpp
在其main方法中,先通过defaultServiceManager()函数获得ServiceManager对象,然后根据dumpsys传进来的参数通过函数checkService来找到具体的service,并执行service的dump方法,达到dumpsys meminfo的目的。

3.2.2 dumpsys meminfo的数据来源

dumpsys meminfo 对应的服务是ActivityManagerService,它从MemBinder类的dump函数开始执行的。总结来说不同类型的Meminfo信息是从不同的位置获取的

3.3 属性值详解

3.3.1 主要属性值价绍

我们先从横坐标开始:

序号字段名称解释
1Pss TotalPss是指实际使用的物理内存,考虑了在进程之间共享RAM页的情况。进程独占的RAM页会直接计入其PSS值,而与其他进程共享的RAM页则会按相应比例计入PSS值。例如,两个进程之间共享的RAM页会将其一半的大小 分别计入这两个进程的PSS中,
Pss Total 就是指某一项Pss的总值。
2Private Dirty进程私有,是仅分配给应用堆的实际RAM,包含了您自己的分配和zygote分配页,这些分配页自从zygote派生您的应用进程以来已被修改。
3Private Clean进程私有的,相对磁盘数据没有使用修改的内存。
4SwapPss Dirty一些Android设备确实使用了内存交换,但它们使用的是内存而不是闪存。Linux有一个称为ZRAM的特性,它可以压缩页面,然后将它们交换到一个特殊的RAM区域,并在需要时再次解压它们。因此,“交换肮脏”中列出的页面很可能是在ZRAM中。

纵坐标

序号属性名说明
1Native Heap在Native Code中使用malloc分配出的内存
2Dalvik HeapDalvik 虚拟机(Java 代码)分配的空间,不包括它自身的开销,Dalvik堆中和zygote进程共享的部分算是sharedDirty
3Dalvik Other类数据结构和索引占据的内存
3Stack堆内存
4Ashmem匿名共享内存,此类内存与cache shrinker关联,可以控制cache shrinker在适当时机回收这些共享内存
5Other dev内存drvier占用的内存
6.so mmap映射的.so(native) 占用的内存
7.jar mmapJava文件代码占用内存
8.apk mmapapk代码占用内存
8.dex mmap映射的.dex(Dalvik 或ART)代码占用的内存
9.oat mmap代码映射占用的RAM量。此映像在所有应用之间共享,不受特定应用影响
10.art mmap堆映像占用的RAM量,此映像在所有应用之间共享,不受特定应用影响。尽管ART映射包含Object实例,它仍然不会计入您的堆大小
11other mmap其他文件占用的内存
  • 我们某个进程实际的Pss 代码实际内存大小是指横坐标为Pss Total,纵列为Total值(其值等于Pss Total 列+SwapPss Dirty 列的总和),如上所示微信总内存为206365K.
  • .art mmap 和.dex mmap 列的详细信息可以通过showmap pid(其他是通过解析/proc/pid/smaps来得到的结果,详细见待整理)来获取更详细的信息,meminfo里面的信息其实也是smaps通过后缀名来统计各类的mmap值。

3.3.2 Heap相关

Heap SizeHeap AllocHeap Free
Native HeapNative Code 使用malloc分配的堆内存总大小,从mallinfo usmblks获得,代表最大总共分配空间从mallinfo uorblks获得,总共已分配空间从mallinfo fordblks获得,代表总共剩余空闲空间
Dalvik Heap虚拟机分配的堆内存总大小,从Runtime totalMemory()获得,Dalvik Heap总共的内存大小Runtime Total Memory()-freeMemory(),Dalvik Heap已分配的内存大小从RunTime freeMemory获得,Dalvik Heap剩余的内存大小

3.3.3 Objects

  • ViewRootImpl
    进程中当前处于活动状态的根视图数量。每个根视图都与一个窗口关联,因此该值有助于您确实与对话框或者其他窗口有关的内存泄漏
  • AppContexts 和 Activities
    您的进程中当前处于活动状态的应用 Context 和 Activity 对象数量。该值可以帮助您快速确定发生泄漏的 Activity 对象,这些对象由于存在对其的静态引用(比较常见)而无法进行垃圾回收。这些对象往往关联了许多其他分配,因此是查找大型内存泄漏的理想工具。当通过Back不断返回退出应用时,Activities值应该等于1

注意
在包含Delvik other 区段的更高平台上,Delvik堆的Pss Total和private Dirty数值不包括Dalvik开销(例如即时编译(JIT和垃圾回收记录)),而更低的版本会在Dalvik中将会其一并列出。

4. 参考资料

https://www.jianshu.com/p/9edfe9d5eb34
https://developer.android.google.cn/studio/command-line/dumpsys#meminfo

以上是关于dumpsys meminfo 详解的主要内容,如果未能解决你的问题,请参考以下文章

dumpsys meminfo 详解

Android 内存优化dumpsys meminfo PID 查看单进程内存信息详解

Android 内存优化dumpsys meminfo PID 查看单进程内存信息详解

Android dumpsys meminfo 流程分析

在android中查询“dumpsys meminfo”

Android使用procrank和dumpsys meminfo分析内存占用情况