• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar

陈文管的博客

分享有价值的内容

  • Android
  • Affiliate
  • SEO
  • 前后端
  • 网站建设
  • 自动化
  • 开发资源
  • 关于

Android 性能监控之内存监控

2020年3月23日发布 | 最近更新于 2023年8月28日

接上一篇Android CPU监控,接着记录下Android内存的监控,包括系统内存和独立进程内存获取方式解析,以及系统内存和独立进程内存信息在C++层获取的实现。

一、Android系统内存解析

1. dumpsys meminfo方式解析

通常获取系统内存是通过dumpsys meminfo的方式:

adb shell dumpsys meminfo

示例输出:

Applications Memory Usage (in Kilobytes):
Uptime: 118719544 Realtime: 448708145

Total PSS by process:
    246,397K: com.autonavi.amapauto (pid 28033 / activities)
    …

Total PSS by OOM adjustment:
    270,031K: Native
        150,621K: surfaceflinger (pid 448)
        …
    136,799K: System
        136,799K: system (pid 1304)
        …
    296,312K: Persistent
        187,565K: com.android.systemui (pid 1579)
        …
    324,776K: Foreground
        246,397K: com.autonavi.amapauto (pid 28033 / activities)
        …
    62,099K: Visible
        15,833K: com.huawei.imonitor (pid 1915)
        …
    160,413K: Perceptible
        56,856K: com.huawei.android.launcher (pid 2676 / activities)
        …
    6,613K: A Services
        6,613K: com.huawei.android.hwouc (pid 26007)
        …
    93,701K: B Services
        51,664K: com.android.settings (pid 1822 / activities)
        …
    257,433K: Cached
        34,026K: com.huawei.hidisk (pid 1085 / activities)
        …

Total PSS by category:
    …

    Total RAM: 2,847,064K (status normal)
    Free RAM: 1,457,677K ( 257,433K cached pss + 1,111,432K cached kernel + 88,812K free)
   Used RAM: 1,691,708K (1,350,744K used pss + 340,964K kernel)
    Lost RAM: -261,582K
          ZRAM: 56,328K physical used for 179,836K in swap (1,572,860K total swap)
         Tuning: 384 (large 512), oom 322,560K, restore limit 107,520K (high-end-gfx)

这边主要参考的是最后几个字段信息,Total Ram总内存,Free Ram可用内存,Used RAM已使用内存。

Lost RAM = Total Ram - Free Ram - Used RAM

Lost RAM是属于误差无法统计的部分,是个负数。也就是说通过dumpsys meminfo获取到的内存信息,计算出的内存占用其实是存在误差的,并不准确。

1)dumpsys meminfo存在的问题

  • 执行耗时:在华为手机上测试,一次dumpsys meminfo的执行耗时在2s左右,如果用在性能监控上,实时性无法保证,车载设备性能更差,耗时更久。
  • 影响system_server进程CPU:测试持续调用命令获取内存信息,在手机上system_server的CPU占用会在8%-11%左右,车镜性能相对手机较差,CPU占用会到18%左右,车机性能更差,CPU会在18% – 33%之间波动。

2)为什么会耗时并影响到system_server进程的CPU?

直接看下dumpsys meminfo源码实现逻辑,命令执行后执行逻辑在dumpsys.cpp里面调用,这边拿Android 6.0的源码来解析,从7.0开始加了很多逻辑。

/frameworks/native/cmds/dumpsys/dumpsys.cpp

int main(int argc, char* const argv[])int main(int argc, char* const argv[]){    
    signal(SIGPIPE, SIG_IGN);    
    sp<IServiceManager> sm = defaultServiceManager();    
    fflush(stdout);    
    if (sm == NULL) { 
        ALOGE("Unable to get default service manager!");        
        aerr << "dumpsys: Unable to get default service manager!" << endl;        
        return 20;    
    }
    Vector<String16> services;    
    Vector<String16> args;    
    bool showListOnly = false;    
    if ((argc == 2) && (strcmp(argv[1], "-l") == 0)) {        
        showListOnly = true;    
    }    
    if ((argc == 1) || showListOnly) {  
        // 命令不带参数获取系统所有服务信息      
        services = sm->listServices();        
        services.sort(sort_func);        
        args.add(String16("-a"));    
    } else {        
       // 命令带参数获取指定服务信息
        services.add(String16(argv[1]));        
        for (int i=2; i<argc; i++) {            
            args.add(String16(argv[i]));        
        }    
    }
    ...
    for (size_t i=0; i<N; i++) {        
        sp<IBinder> service = sm->checkService(services[i]);        
        if (service != NULL) {            
            if (N > 1) {                
                aout << "------------------------------------------------------------"                        
                "-------------------" << endl; 
                aout << "DUMP OF SERVICE " << services[i] << ":" << endl;            
            }   
            // 执行服务的dump方法,输出dump内容         
            int err = service->dump(STDOUT_FILENO, args);            
            if (err != 0) {                
                aerr << "Error dumping service info: (" << strerror(err)      
                  << ") " << services[i] << endl; 
            }        
        } else {           
            aerr << "Can't find service: " << services[i] << endl;        
        }    
    }
    return 0;
}

接着看下实际执行dumpsys的地方,在ActivityManagerService.java类里面。

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

实际执行dump的逻辑在dumpApplicationMemoryUsage方法里面,以下代码已简化,保留几处关键信息

final void dumpApplicationMemoryUsage(FileDescriptor fd, PrintWriter pw, String prefix, String[] args,
                  boolean brief, PrintWriter categoryPw) {
    ...
    if (!isCheckinRequest && procs.size() > 1 && !packages) {
        // If we are showing aggregations, also look for native processes to
        // include so that our aggregations are more accurate.
        updateCpuStatsNow();
        mi = null;
        synchronized (mProcessCpuTracker) {
            final int N = mProcessCpuTracker.countStats();
            for (int i=0; i<N; i++) {
                // 遍历所有进程统计数据
                ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i);
                if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) {
                if (mi == null) {
                    mi = new Debug.MemoryInfo();
                }
                if (!brief && !oomOnly) {
                    Debug.getMemoryInfo(st.pid, mi);
                } else {
                    mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
                    mi.nativePrivateDirty = (int)tmpLong[0];
                }
                final long myTotalPss = mi.getTotalPss();
                totalPss += myTotalPss;
                nativeProcTotalPss += myTotalPss;
                MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")",
                           st.name, myTotalPss, st.pid, false);
                procMems.add(pssItem);
                nativePss += mi.nativePss;
                dalvikPss += mi.dalvikPss;
                for (int j=0; j<dalvikSubitemPss.length; j++) {
                    dalvikSubitemPss[j] += mi.getOtherPss(Debug.MemoryInfo.NUM_OTHER_STATS + j);
                }
                otherPss += mi.otherPss;
                for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) {
                    long mem = mi.getOtherPss(j);
                    miscPss[j] += mem;
                    otherPss -= mem;
                 }
                 oomPss[0] += myTotalPss;
                 if (oomProcs[0] == null) {
                    oomProcs[0] = new ArrayList<MemItem>();
                 }
                 oomProcs[0].add(pssItem);
             }
        }
    }
    ...
    if (!brief) {
        if (!isCompact) {
            pw.print("Total RAM: "); 
            pw.print(memInfo.getTotalSizeKb());
            pw.print(" kB (status ");
            switch (mLastMemoryLevel) {
                case ProcessStats.ADJ_MEM_FACTOR_NORMAL:
                     pw.println("normal)");
                     break;
                case ProcessStats.ADJ_MEM_FACTOR_MODERATE:
                     pw.println("moderate)");
                     break;
                case ProcessStats.ADJ_MEM_FACTOR_LOW:
                     pw.println("low)");
                     break;
                case ProcessStats.ADJ_MEM_FACTOR_CRITICAL:
                     pw.println("critical)");
                     break;
                default:
                     pw.print(mLastMemoryLevel);
                     pw.println(")");
                     break;
            }
            pw.print(" Free RAM: "); 
            pw.print(cachedPss + memInfo.getCachedSizeKb()
                        + memInfo.getFreeSizeKb()); pw.print(" kB (");
            pw.print(cachedPss); pw.print(" cached pss + ");
            pw.print(memInfo.getCachedSizeKb()); pw.print(" cached kernel + ");
            pw.print(memInfo.getFreeSizeKb()); pw.println(" free)");
       } else {
            pw.print("ram,"); 
            pw.print(memInfo.getTotalSizeKb());
            pw.print(",");
            pw.print(cachedPss + memInfo.getCachedSizeKb() + memInfo.getFreeSizeKb()); 
            pw.print(",");
            pw.println(totalPss - cachedPss);
       }
   }
   if (!isCompact) {
       pw.print(" Used RAM: "); 
       pw.print(totalPss - cachedPss + memInfo.getKernelUsedSizeKb()); 
       pw.print(" kB (");
       pw.print(totalPss - cachedPss);
       pw.print(" used pss + ");
       pw.print(memInfo.getKernelUsedSizeKb()); 
       pw.print(" kernel)\n");
       pw.print(" Lost RAM: "); 
       pw.print(memInfo.getTotalSizeKb() - totalPss - memInfo.getFreeSizeKb() 
           - memInfo.getCachedSizeKb() - memInfo.getKernelUsedSizeKb()); pw.println(" kB");
   }
    ...
  }
}

从源码可以看出,耗时是因为dumpApplicationMemoryUsage里面有很多for循环遍历逻辑,dump逻辑执行的时候要逐个进程遍历统计PSS值,而ActivityManagerService属于系统服务,这就是为什么会影响到system_server进程的CPU。

2. cat /proc/meminfo方式解析

adb shell cat /proc/meminfo

示例结果输出如下,字段的定义可以查看Proc 官方文档:

MemTotal: 2847064 kB
MemFree: 93768 kB
MemAvailable: 1159236 kB
Buffers: 4188 kB
Cached: 1072436 kB
SwapCached: 57720 kB
Active: 1171184 kB
...

这边需要关注的只有前几个字段,MemTotal、MemFree、Buffers、Cached。MemFree表示当前空闲内存,Buffer和Cache是已被操作系统分配用于缓存块数据和文件数据的缓存。

从操作系统层面看Buffer和Cached是已被占用的。

MemUsed = MemTotal - MemFree

从用户层面来看,Buffers和Cached属于保留空间。

MemUsed = MemTotal - MemFree - Buffers - Cached

也就是一般在应用程序的内存监控中,用以下方式来计算系统内存的使用率:

MemUsedRate = (MemTotal - MemFree - Buffers - Cached)/ MemTotal

通过读取/proc/meminfo的方式来监控系统内存更能实时反馈出系统内存的使用情况,只涉及到信息的读取,不耗时,且不影响system_server进程。

二、Android系统内存信息获取C++实现

output是Socket输出对象,具体逻辑可以查看前面Android 性能监控之CPU监控中的Socket服务端C++实现。

// 读取系统总内存信息
read_procs_all("/proc/meminfo", output);
/**
 * 用于读取系统内存信息
 */
static int read_procs_all(char* fileName, FILE* output){
    FILE *statFile;
    char buf[MAX_LINE], result[BUF_SIZE];
    memset(buf, 0, MAX_LINE);
    memset(result, 0, BUF_SIZE);
    int needBreak = -1;
    statFile = fopen(fileName, "r");
    if (!statFile) {
        printf("Could not open %s .\n", fileName);
        return 1;
    }
    while(fgets(buf, MAX_LINE, statFile) != NULL){
        /**
         * 系统内存从"SwapCached:"开始的地方截取,去掉后面的无用字段
        */
        if(needBreak != 0){
            needBreak = memcmp(buf, "SwapCached:", 11);
            if(needBreak == 0){
                break;
            }
        }
        strcat(result, buf);
    }
    fprintf(output, "%s", result);
    fclose(statFile);
    return 0;
}

三、Android独立进程内存获取解析

adb shell dumpsys meminfo <package>

目前除了以上adb命令方式没有更好的方法来获取独立进程的内存信息。

还有另外一种方式来读取独立进程的内存信息,不过前提是设备具备Root权限,另外这种方式很容易导致IO问题,最后读取到的信息都是乱码。实现方式就是通过读取/proc/[pid]/maps文件中的内存映射地址信息,去匹配读取/proc/<pid>/mem文件中的内存信息。具体实现可以参考下下面两篇文章:

  • How do I read from /proc/$pid/mem under Linux?
  • Linux 101: How to hack your process’ memory

dumpsys meminfo <package>独立进程内存输出示例:

Applications Memory Usage (in Kilobytes):
Uptime: 128428913 Realtime: 458417514

** MEMINFO in pid 28033 [com.android.test] **
                 Pss  Private Private SwapPss Heap  Heap    Heap
                 Total  Dirty  Clean  Dirty   Size  Alloc   Free
                ------ ------ ------ ------ ------ ------ ------
   Native Heap   77946  74156  3772       0 135424  93392  42031
   Dalvik Heap    9893   3964  5860       0  15861   9517   6344
  Dalvik Other    1725   1604   116      88 
         Stack    1372   1268   104     376 
        Ashmem      23      0     0       0 
     Other dev       8      0     8       0 
      .so mmap   27183    432 25228     661 
     .apk mmap     351      0    56       0 
     .ttf mmap    1325      0  1236       0 
     .dex mmap    1381      0   792       4 
     .oat mmap    3913      0  1908       0 
     .art mmap    2511    672   812     163 
    Other mmap       9      4     0       1 
     GL mtrack  100784 100784     0       0 
       Unknown     706    492   208     508 
         TOTAL  230931 183376 40100    1801 151285 102909  48375

   App Summary
           ...
   Objects
           ...
   SQL
           ...

这边主要看TOTAL总内存值,如果应用存在内存泄漏,总内存就会出现持续增长的状态。

测试通过dumpsys meminfo <package>读取独立进程内存信息对system_server的CPU占用几乎没影响,间隔1S持续调用获取信息,system_server的CPU占用几乎维持在1%左右,偶尔一两次会出现2%或3%的情况。

如果要实现自动化监控,可以监控内存增长曲线的斜率,如果只是场景切换,斜率会先增长,之后再逐步降低。如果是内存泄漏,斜率会维持在一个数值,或者持续增大。可以定义一个计数器来统计斜率增长曲线(注意不是内存增长曲线)的增长和降低,斜率增加则计数+1,斜率降低计数-1,设定一个阈值比如计数>60的时候触发内存泄漏Hprof文件的抓取。

四、Android独立进程内存获取C++实现

这边注意一个点,在C++层,可以用popen来调用shell命令行命令并获取命令行的输出内容。

popen()函数通过创建一个管道,调用fork()产生一个子进程,执行一个shell以运行命令来开启一个进程。可以通过这个管道执行标准输入输出操作。这个管道必须由pclose()函数关闭, 而不是fclose()函数(若使用fclose则会产生僵尸进程)。pclose()函数关闭标准I/O流,等待命令执行结束,然后返回shell的终止状态。如果shell不能被执行,则pclose()返回的终止状态与shell已执行exit一样。

output是Socket输出对象,具体逻辑可以查看前面Android 性能监控之CPU监控中的Socket服务端C++实现。

//monitorProcessPkg 为监控的进程包名参数
char processMem[128];
sprintf(processMem, "dumpsys meminfo %s", monitorProcessPkg);
executeCMD(processMem, output);

static int executeCMD(const char *cmd, FILE* output) {
    char buf[MAX_LINE], result[BUF_SIZE], cmdBuf[MAX_LINE];
    memset(buf, 0, MAX_LINE);
    memset(cmdBuf, 0, MAX_LINE);
    memset(result, 0, BUF_SIZE);
    FILE *ptr;
    strcpy(cmdBuf, cmd);
    int needBreak = -1;
    if((ptr=popen(cmdBuf, "r"))!=NULL)
    {
        while(fgets(buf, MAX_LINE, ptr)!=NULL)
        {
           /**
            * 独立进程内存输出的内容过多会导致Socket分批传送,代码上无法对分批次传送的时序问题做容错,会导致无法一次性解析正确的结果。
            * 此处选择在"SQL" 或 "DATABASES" 开始处做截断
            */
            if(memcmp(buf, " SQL", 4) == 0 || memcmp(buf, " DATABASES", 10) == 0){
                break;
            }
            strcat(result, buf);
            if(strlen(result) > BUF_SIZE){
                break;
            }
        }
        fprintf(output, "%s", result);
        pclose(ptr);
        ptr = NULL;
        return 1;
    } else {
        printf("Performance monitor popen %s error\n", cmdBuf);
        return -1;
    }
    return 0;
}

五、参考资料

C++执行命令行指令并获取命令行执行后的输出结果

Interpreting /proc/meminfo and free output for Red Hat Enterprise Linux 5, 6 and 7

Android内存泄漏监控和优化技巧总结

android dalvik heap 浅析

Linux Proc说明文档

How do I read from /proc/$pid/mem under Linux?

Linux 101: How to hack your process’ memory

扩展阅读:

Android 性能监控之CPU监控

Android OOM问题分析

转载请注明出处:陈文管的博客 – Android 性能监控之内存监控

扫码或搜索:文呓

博客公众号

微信公众号 扫一扫关注

文章目录

  • 一、Android系统内存解析
    • 1. dumpsys meminfo方式解析
    • 2. cat /proc/meminfo方式解析
  • 二、Android系统内存信息获取C++实现
  • 三、Android独立进程内存获取解析
  • 四、Android独立进程内存获取C++实现
  • 五、参考资料
博客公众号

闽ICP备18001825号-1 · Copyright © 2025 · Powered by chenwenguan.com