Android提供了一个名为meminfo的小工具帮助应用分析自身的内存占用,并且在4.4还新增了memtrack HAL模块,SoC厂商通过实现memtrack模块,让meminfo可以获取GPU相关的一些内存分配状况。了解meminfo的实现,对我们更深入 了解应用的内存占用状况是很有帮助的。而这篇文章的目的就是分析Android 4.4 meminfo的内部实现源码,让开发者通过这些信息可以更了解自己应用的内存占用状况。

在控制台输入命令”adb shell dumpsys meminfo YOUR-PACKAGE-NAME”,可以看到类似下图的结果:

** MEMINFO in pid 14120[com.UCMobile.test]**PssPrivatePrivateSwappedHeapHeapHeapTotalDirtyCleanDirtySizeAllocFree------------------------------------------NativeHeap1878861878720032523217409338594DalvikHeap24801244440041476358995577DalvikOther70070000Stack50850800Other dev    335643260040.so mmap     9019124472680.apk mmap      1010160.ttf mmap     133006960.dex mmap     2248022480                               code mmap      98501880                              image mmap     1182908120Other mmap      13041080Graphics255042550400                                      GL     2196219600Unknown324763247600                                   TOTAL   32263030845610540036670820999244171

实际的调用代码入口在android.os.Debug.java和对应的CPP文件 android_os_Debug.cpp,Debug.java的getMeminfo方法实际上调用了android_os_Debug.cpp的 android_os_Debug_getDirtyPagesPid方法。

staticvoid android_os_Debug_getDirtyPagesPid(JNIEnv*env, jobject clazz,        jint pid, jobject object){stats_t stats[_NUM_HEAP];    memset(&stats,0,sizeof(stats));    load_maps(pid, stats);struct graphics_memory_pss graphics_mem;if(read_memtrack_memory(pid,&graphics_mem)==0){...}...}staticvoid load_maps(int pid,stats_t* stats){char tmp[128];    FILE *fp;    sprintf(tmp,"/proc/%d/smaps", pid);    fp = fopen(tmp,"r");if(fp ==0)return;    read_mapinfo(fp, stats);    fclose(fp);}

从上面的代码可以看到,android_os_Debug_getDirtyPagesPid方法先调用了load_maps方法,而 load_maps方法要做的事情也很简单,它打开/proc/PID/smaps虚拟文件,读取里面的信息,在已ROOT的设备上,我们可以通过 “adb shell cat /proce/PID/smaps”直接将这个虚拟文件的信息打印在控制台上。

80ff5000-810f2000 rw-p 0000000000:000[stack:12211]Size:1012 kBRss:4 kBPss:4 kB...81100000-811a4000 rw-s 000f400000:0b6285/dev/kgsl-3d0Size:656 kBRss:652 kBPss:352 kB...811d1000-811e0000 rw-p 0000000000:000[anon:libc_malloc]Size:60 kBRss:60 kBPss:60 kB...Name:[anon:libc_malloc]

“adb shell cat /proce/PID/smaps”输出的信息如上图所示,它实际上是应用的userspace地址空间的内存分配表,记录了应用分配的每一块内存的地 址,类别,大小等信息,而load_maps方法调用read_mapinfo方法从这个表里面读出每一块内存的分配信息,分类进行累加,得出 Native Heap,Dalvik Heap等各个类别的内存占用。

但是应用所使用的全部内存里面,有一些内存块是不映射到进程的userspace地址空间的(主要是GPU所使用的内存),这些内存块的信息在 smaps里面无法找到,所以在Android 4.4里面新增了一个memtrack的HAL模块由SoC厂商实现,如果SoC厂商实现了memtrack模块,meminfo则可以通过 libmemtrack的调用获取一些跟GPU相关的内存使用信息。所以我们看到android_os_Debug_getDirtyPagesPid方 法通过调用read_memtrack_memory方法来读取Graphics,GL这两项的内存使用信息。

/* * Uses libmemtrack to retrieve graphics memory that the process is using. * Any graphics memory reported in /proc/pid/smaps is not included here. */staticint read_memtrack_memory(struct memtrack_proc* p,int pid,struct graphics_memory_pss* graphics_mem){int err = memtrack_proc_get(p, pid);...ssize_t pss = memtrack_proc_graphics_pss(p);...    graphics_mem->graphics = pss /1024;    pss = memtrack_proc_gl_pss(p);...    graphics_mem->gl = pss /1024;    pss = memtrack_proc_other_pss(p);...    graphics_mem->other = pss /1024;return0;}

read_memtrack_memory方法的实现如上图所示,它读取了Graphics,GL,Other这三类内存信息,而这三个类别的定义在hardware/memtrack.h里面。

/* * The Memory Tracker HAL is designed to return information about device-specific * memory usage.  The primary goal is to be able to track memory that is not * trackable in any other way, for example texture memory that is allocated by * a process, but not mapped in to that process' address space. * A secondary goal is to be able to categorize memory used by a process into * GL, graphics, etc.  All memory sizes should be in real memory usage, * accounting for stride, bit depth, rounding up to page size, etc. * * A process collecting memory statistics will call getMemory for each * combination of pid and memory type.  For each memory type that it recognizes * the HAL should fill out an array of memtrack_record structures breaking * down the statistics of that memory type as much as possible.  For example, * getMemory(, MEMTRACK_TYPE_GL) might return: * { { 4096,  ACCOUNTED | PRIVATE | SYSTEM }, *   { 40960, UNACCOUNTED | PRIVATE | SYSTEM }, *   { 8192,  ACCOUNTED | PRIVATE | DEDICATED }, *   { 8192,  UNACCOUNTED | PRIVATE | DEDICATED } } * If the HAL could not differentiate between SYSTEM and DEDICATED memory, it * could return: * { { 12288,  ACCOUNTED | PRIVATE }, *   { 49152,  UNACCOUNTED | PRIVATE } } * * Memory should not overlap between types.  For example, a graphics buffer * that has been mapped into the GPU as a surface should show up when * MEMTRACK_TYPE_GRAPHICS is requested, and not when MEMTRACK_TYPE_GL * is requested. */enum memtrack_type {    MEMTRACK_TYPE_OTHER =0,    MEMTRACK_TYPE_GL =1,    MEMTRACK_TYPE_GRAPHICS =2,    MEMTRACK_TYPE_MULTIMEDIA =3,    MEMTRACK_TYPE_CAMERA =4,    MEMTRACK_NUM_TYPES,};

Graphics对应了MEMTRACK_TYPE_GRAPHICS,GL对应了MEMTRACK_TYPE_GL,而Other实际上是 MEMTRACK_TYPE_OTHER,MEMTRACK_TYPE_MULTIMEDIA,MEMTRACK_TYPE_CAMERA这三项之和。 memtrack是由SoC厂商实现的,在AOSP的源码里面我们可以找到高通的实现源码,在msm8974/libmemtrack/kgsl.c里 面。

int kgsl_memtrack_get_memory(pid_t pid,enum memtrack_type type,struct memtrack_record *records,size_t*num_records){...    sprintf(tmp,"/d/kgsl/proc/%d/mem", pid);    fp = fopen(tmp,"r");...if(type == MEMTRACK_TYPE_GL){        sprintf(tmp,"/proc/%d/smaps", pid);        smaps_fp = fopen(tmp,"r");...}while(1){unsignedlong uaddr;unsignedlong size;char line_type[7];int ret;if(fgets(line,sizeof(line), fp)== NULL){break;}/* Format:         *  gpuaddr useraddr     size    id flags       type            usage sglen         * 545ba000 545ba000     4096     1 ----p     gpumem      arraybuffer     1         */        ret = sscanf(line,"%*x %lx %lu %*d %*s %6s %*s %*d\n",&uaddr,&size, line_type);if(ret !=3){continue;}if(type == MEMTRACK_TYPE_GL && strcmp(line_type,"gpumem")==0){bool accounted =false;/*             * We need to cross reference the user address against smaps,             *  luckily both are sorted.             */while(smaps_addr <= uaddr){unsignedlong start;unsignedlongend;unsignedlong smaps_size;if(fgets(line,sizeof(line), smaps_fp)== NULL){break;}if(sscanf(line,"%8lx-%8lx",&start,&end)==2){                    smaps_addr = start;continue;}if(smaps_addr != uaddr){continue;}if(sscanf(line,"Rss: %lu kB",&smaps_size)==1){if(smaps_size){                        accounted =true;                        accounted_size += size;break;}}}if(!accounted){                unaccounted_size += size;}}elseif(type == MEMTRACK_TYPE_GRAPHICS && strcmp(line_type,"ion")==0){            unaccounted_size += size;}}...}

kgsl_memtrack_get_memory是memtrack的getMemory方法的具体实现,我们可以看到它实际上是读取一张内部的 GPU内存分配表的信息(虚拟文件/d/kgsl/proc/PID/mem),在已ROOT的设备上,我们可以通过“adb shell cat /d/kgsl/proc/PID/mem”将这张内存分配表的信息打印到控制台上,如下图所示:

gpuaddr useraddr     size    id flags       type            usage sglen7565e0000000000040961----p     gpumem      arraybuffer     1756bc00000000000655362-r--p     gpumem          command    16756cd00000000000655363-r--p     gpumem          command    16756de00000000000655364-r--p     gpumem          command    16756fb0000000000040965----p     gpumem               gl     175fe2000000000002621446----p     gpumem               gl    64760230000000000081927----p     gpumem               gl     2760260000000000081928----p     gpumem               gl     2760290000000000040969----p     gpumem          texture     1...94d7100000000000131072362----p     gpumem  vertexarraybuff    3294da000000000000667648176--l-p     gpumem          texture   16394e4400000000000131072363----p     gpumem           any(0)3294e6500000000000131072364----p     gpumem           any(0)32c0000000 000000001726873631--L--        ion        egl_image  4216c1100000 00000000825753636--L--        ion      egl_surface    21c1900000 000000008257536164--L--        ion      egl_surface    21c2100000 000000008257536175--L--        ion      egl_surface    21

其中ion类型(由ION内存分配器分配的内存)的内存块统计到Graphics类别里面,从上图我们可以看到有三块egl_surface,它们 对应应用所使用的窗口的三个Buffer,还有一个egl_image暂时不清楚用途,这些都是应用启动后Android自动分配的。gpumem类型的 内存块统计到GL类别里面,包括GL里面的纹理(texture),各种shader,vertex buffer等等。另外,因为有些内存块映射到了userspace,有些则没有映射,所以映射到userspace的内存块会被标记为 accounted,避免meminfo重复计数,meminfo最终显示的Graphics和GL的内存值是哪些没有映射到userspace的内存块 的大小之和。

更多相关文章

  1. Android Intent调用方法总结
  2. Android 应用程序退出后不在运行列表中显示的方法
  3. ffmpeg neon优化必看!!android下编译ffmpeg with neon的正确方法(已
  4. 信息浏览器从Android的浏览器中传递cookie数据到App中信息浏览器
  5. Android ADB使用方法
  6. Handle详解和使用方法
  7. Android下引用系统库的方法及问题

随机推荐

  1. Android(安卓)Adapter
  2. Android更新真快——Fragment 代替 TabAc
  3. Qt Ubuntu 配置 Android(安卓)环境变量
  4. android录制视频流程
  5. 给按钮设置点击变色
  6. 学习文档android
  7. Android培训班(49)
  8. 【FAQ】Ubuntu环境下ant编译android代码
  9. Android(安卓)Timer类的使用
  10. undefined reference to '__android_log_