Android meminfo字段详解

对于Android内存的统计拆分,需要从系统到进程,再细分到内存分类。Android的内存通常被多个进程 共享,一个进程占用的内存涉及到虚拟内存、物理内存、私有、共享、图形显存等等。想要详细统计一 个进程的内存占用,就需要了解Android的内存管理机制和对内存的分类拆解。

一、Android内存管理

Android内存的管理核心是paging和memory-mapping(mmap).

1.paging

Andoid系统中使用虚拟内存地址来索引内存,虚拟内存被划分为固定大小的page页,典型的页大小为 4K。内存分配最开始都是在虚拟内存上分配,当需要访问这段内存的时候,如果发现它没有存在于物理 内存上(即MMU不能找到这个虚地址va对应的物理地址pa),即发生了缺页(page fault).,缺页有几种 可能:

1.Bug,程序访问了它不应该访问的虚地址空间,android系统会触发访问不合法,ki掉进程。 2.Va是合法的,但是这块va对应的pa还从来没有被分配出来过(例如你mmap的一段内存空间,但是从 来没用过,这时第一次在这块内存上写入),这叫做lazy-allocation,这时系统会真正分配一段物理内存 给你用,然后在页表上对应好这段pa和va。注意第一次写入这里才算真正占用了物理内存,mmap的分 配并不算。 3.Va是合法的,但是这va对应的pa内容当前并没有在物理内存上,而是被swap到一个backup的file上, 这时系统会给这个page在pa上分配物理内存,然后将这块内容从文件读回到物理内存上(swap-in)。

2.swap zram

典型的iux系统的虚拟内存都有swap操作,即一段物理内存在一段时间不用的时候,为了节省物理内存 将他们备份到它的外ackup file.上,一段时间后缺页时再换回。 但是在android上大多数情况是没有这套swap机制的,因为对于移动端的1O代价太大,所以大多数情况 被映射到pa的page是不能被swap的。只有一种情况除外,即如果这段虚拟地址段具有backup file,并且 当他被swap-in到pa后是只读的,那么它是有机会被swap-out回disk的,因为swap这种内存的代价很 小,他们不会在物理内存上被更改,通常这类情况包括那些代码文件的mmap(如dex、so等)。 此外android.上还使用了一种特殊的ZRAM机制来压缩一些物理内存上的page,但是并不把他们swap-out 到disk上,而是仍然在ram中,这是一段被压缩了的page,系统会选择压缩一些page存储在内存中以腾 出一些物理内存的占用。

3.mmap

Linux.上一个重要的特性是memory mapping。如上面所述,va和pa之间通过mmu建立了一个对应关系, 通过page fault来触发pa的分配,此外va还会对应一个backup file,作为swap-in/out时的备份存储。这个 va和backup file之间的对应关系就叫做nemory mapping,我们可以利用这个机制做文件读取。 第一种是映射文件到内存做读取,这时提供一个文件句柄,然后将文件内容映射到一段虚拟内存地址空 间,这样就做到了通过访问虚拟地址空间-造成缺页一swapin backup file的方式来根据需要读取文件。 他相比传统的文件读取效率更高。 第二种是创建一个匿名映射,匿名映射没有backupfile,只是单纯分配一块虚拟内存空间,这其实是 android.上调用new一个对象做的事情,我们new一块int的数组,事实上在new后很可能是通过mmap分 配了一块虚拟内存空间,而只有当第一次写入的时候才触发缺页而占用真正的物理内存,所以统计 android的物理内存占用不是看new了多少,或者文件映射了多少,而是实际上虚拟内存缺页了多少。 本成224

二、meminfo数据详解

通过命令dumpsys meminfo package._name|pid,查看指定进程的内存使用情况。通过输出的信 息,可以看出来应用在内存哪里分配出现了问题,比如native heap分配高,就要查一下自己的native部 分的代码哪里分配后没有及时释放。 dumpsys位于/frameworks/native/cmds/dumpsys。运行时它首先后获得ServiceManager,然后 根据参数查找服务,找到后binderi调用服务的dump方法。meminfo位于/system/memory/libmeminfo,是 系统的一个服务(内部则是一个MemBinder的类实现的),当dumpsys找到这个服务后,调用dump方法,通 过传递的参数,显示出进程内存使用情况。

下面是system_serveri进程的meminfo,

这里面可以看到oss privatedirty privateclean swapped diry这几个重要的指标。

pss
Proportional Set Size
进程实际占用的物理内存大小,但是android内存涉及到在系统中同其他进程共享一部分库(可能是so,可能是字体文件等的mmp),所以这里面会考虑这个因素计算你的进程平摊的这部分共享库的大小,这里的proportional就意味着统计了这个平摊后的物理内存。这是衡量进程占用内存的最真实指标。
rss
Resident Set Size
常驻内存大小,是单个进程实际占用的内存大小;RSS易被误导的原因在于,它包括了该进程所使用的所有共享库的全部内存大小。对于单个共享库,尽管无论多少个 进程使用,实际该共享库只会被装入内存一次。对于单个进程的内存使用大小, RSS不是一个精确的描述。
Rss=Shared Clean+Shared Dirty+Private Clean+Private_Dirty
uss
Unique Set Size
进程独占的内存
Vss
Virtual Set Size
VSS=RSS+未分配实际物理内存
内存的大小关系:VSS>=RSS>=PSS>=USS
swappablePss
能够被共享的内存。
sharing_proportion =(pss-uss)/(shared_clean shared_dirty)
swapable_pss=(sharing_proportion shared_clean)+private_clean
privateDirty
privateClean
它们指的是每个进程独有的内存页面。PrivateDirty指的是进程独有的脏页面,即进程
已经修改但尚未提交到磁盘的页面;PrivateClean指的是进程独有的干净页面,即进程已经提交到磁盘的页面。
sharedDirty
sharedClean
指的是进程和别的进程共享的内存页面。sharedDirty是该过程和至少一个其他过程所
引用的映射中的页面,并且由这些过程中的至少一个过程写入的页面;sharedClean
是指该进程和至少一个其他进程已引用但未由任何进程修改的映射中的页面
Swapped Dirty
指的并不是被swap-out出去的内存,而是android?系统中zram机制压缩掉的部分
Private Dirty部分的不常用物理内存,这个重要度等同于Private Dirty,因为哪些
Private Dirty会被压缩不能被控制,所以这部分多通常也是Private Dirty的。

因为PSS中已经包含了Private Dirtyi和Private Clean,但是没包含swapped dity,所以最终衡量你的 进程对物理内存的占用应该是取PSS+Swapped Dirty 这里统计的所有值都是物理内存大小,即缺页的那些虚拟内存的大小,而不是真正虚拟内存的大小, 注意这里看到的所有内存大小都是真真实实占着你的物理内存的。比如,new1个int[1024],在对这 块内存写入之前它都不会占用物理内存并体现在这里。

1.数据来源

根据属性的不同,进程的虚拟地址空间被划分成若干个vma,每一个vma通过vm_next和vm_prev组 成双向链表,链表头位于进程的taskmmmmap。通过cat proc//maps可以得到该进 程的maps数据如下。每一行的vma对应着起始地址、结束地址、属性、偏移地址、主从设备号、 inode编号和文件名。当通过proc接口读取进程的smaps.文件时,内核会首先找到该进程的vma链表 头,遍历链表中的每一个vma,通过walk_page_vma统计这块vma的使用情况,最后显示出来。

smaps文件是基于maps的扩展,它展示了一个进程的内存消耗,比同一目录下的maps文件更为详细。 如下是maps中一条vma的详细数据,全部数据同样可通过cat/proc//smaps获取。

7fce3a5000-7fceba4000  rw-p   00000000 00:00 0
Size:                  8188  kB
KernelPagesize:           4  kB
MMUPagesize:              4  kB
Rss:                    156  kB
Pss:                    152  kB
Shared Clean:             0  kB
Shared_Dirty:             4  kB
Private Clean:            0  kB
Private_Dirty:          152  kB
Referenced:             156  kB
Anonymous:              156  kB
LazyFree:                 0  kB
AnonHugePages:            0  kB
ShmemPmdMapped:           0  kB
FilePmdMapped:            0  kB
Shared_Hugetlb:           0  kB
Private_Hugetlb:          0  kB
Swap:                     0  kB
SwapPss:                  0  kB
Locked:                   0  kB
THPeligible:              0
VmFlags:rd wr mr mw me gd ac

AMS中dump meminfo功能最终也是通过JNl调用libmeminfo读取进程smaps的节点。

在ProcessRecord中查找。

@GuardedBy(anyof {"mService","mProcLock"})
ArrayList<ProcessRecord>collectProcessesLOSP(int start,boolean allPkgs,
String[]args){
	ArrayList<ProcessRecord>procs;
	if(args!=null&args.length>start//dump指定进程
		&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.getpid()>0 &proc.getpid()==pid){
            	procs.add(proc);
        	} else if (allPkgs &proc.getpkgList()!=null
       		 		&& proc.getPkgList().containsKey(args[start])){
        		procs.add(proc);
        	} else if (proc.processName.equals(args[start])){
        		procs.add(proc);
          	}
        if (procs.size()<=0){
        	return null;
        }
 
	} else[  //dump所有进程
		procs = new ArrayList<ProcessRecord>(mLruProcesses);
 
	return procs;
}	

Java进程会额外dump一些Object、SQL、DATABASES:等信息

pw.println("objects");
printRow(pw, TWO_COUNT_COLUMNS, "Views:"viewInstancecount,"ViewRootImpl:"
viewRootInstanceCount);
 
printRow(pw,TWO_COUNT_COLUMNS,"AppContexts:"appContextInstanceCount,
"Activities:"activityInstanceCount);
 
printRow(pw,TWO_COUNT_COLUMNS,"Assets:"globalAssetcount,
"AssetManagers:"globalAssetManagerCount);
 
printRow(pw,TWO_COUNT_COLUMNS,"Local Binders:"binderLocalobjectcount,
"Proxy Binders:"binderProxyobjectCount);
 
printRow(pw,TWO_COUNT_COLUMNS,"Parcel memory:"parcelsize/1024,
"Parcel count:"parcelCount);
 
printRow(pw,TWO_COUNT_COLUMNS,"Death Recipients:"binderDeathobjectCount,
"OpenSSL Sockets:"openSslSocketCount);
 
printRow(pw,ONE_COUNT_COLUMN,"Webviews:"webviewInstanceCount);
 
// SQLite mem info
pw.println("")
pw.println("SQL");
printRow(pw,ONE_COUNT_COLUMN,"MEMORY_USED:"stats.memoryUsed 1024);
printRow(pw,TWO_COUNT_COLUMNS,"PAGECACHE_OVERFLOW:"
stats.pageCacheoverflow / 1024,"MALLOC_SIZE:"stats.largestMemAlloc / 1024);

2.内存分类

Native Heap: 这是C/C++层直接通过malloc分配的内存 Dalvik Heap/other: 这是android的java虚拟机分配的内存,也就是java部分分配的, Stack: 栈分配内存 Ashmem: 进程的匿名共享内存Anonymous Shared Memory GFX dev: 通俗来说是显存,Android显存和内存在同样的物理设备上,所以统计的总内存是包括显存 的,至于adb如何知道哪些是显存,是因为gles和egl的so库在分配显存的时候也是使用的带backup文件 的mmap,adb只是简单的统计了所有gles和egl的mmap将其视为显存,这部分通常就是在gpu上的资 源,gpu上的资源由很多种,占比较多的就是texture,buffer,shader programe,通常是游戏应用。 Other dev:除显卡外所有其他硬件设备的mmap后的物理内存,可能包括声卡等,通常不多。

.so mmap: 这个就是so库本身文件mmap占用的物理内存,随着程序运行会逐渐的读取so文件,造成和 缺页的部分就是在物理内存产生占用,这部分大就是so库太大了,但是这部分因为有很多是readonly的 mmap,所以有更大的机会被swap-out出去。 apk.dex oat.art mmap:这些都是android程序文件本身被mmap占用的内存,和so的性质差不多。 Other mmap:是所有除了上面的之外其他的所有非匿名方式的mmap,想要知道是什么可以通过下面 要讲的命令查看。 Unkonwn: 在meminf中它指除了部分已知的有名字的匿名映射(以anon:为前缀,例如”[anon:dalvik-”) 之外的所有的匿名mmap,还有其他的未命名的vma(name字段不显示),因为无法分辨来源,就统计 在unkown中。例如,看到anon:dalvik-*,那它一般是dalvik虚拟机的分配,这个最终会被统计到 meminfo的dalvik里。

anon: 这个就是匿名mmap映射,除了部分已知的匿名映射如”[anon:dalvik-”,” [anon:stack and tls:“等,其他匿名映射所占用的内存最终会被统计到neminfo的unkown里。 anon:libc_malloc这个是通过malloc方法进入的mmap,即所有的new,malloc调用。 Kgsl-3d:则是gles对显存硬件的虚拟内存映射,换句话说就是显存,meminf正是统计这个标签来获取 gfx的大小o

更详细的拆解在android_os_Debug.cpp中。如下表所示,是对smaps?字段的详尽匹配规则。

子类值标签****值文件名
HEAP_NATIVE”Native Heap”**[heap] [anon:libc_malloc] [anon:scudo: [anon:**GWP-ASan
HEAP_STACK”Stack”[stack [anon:stack_and_tls:
HEAP_SO”.so mmap”.so
HEAP_JAR”.jar mmap”.jar
HEAP_APK**“.**apk mmap”**.**apk
HEAP_TTF**“.**ttf mmap”**.**ttf
HEAP_DEX”.dex mmap”
HEAP_DEX_APP_DEX**“.**App dex”.odex .dex
HEAP_DEX_BOOT_VDEX”.Boot vdex”**.vdex && @boot or /boot or |**apex
HEAP_DEX_APP_VDEX**“.**App vdex”.vdex
HEAP_OAT”.oat mmap”.oat
HEAP_ART”.art mmap”.art .art]
HEAP_ART_BOOT**“.**App art”system@framework@boot* and system/framework/boot|apex*
HEAP_ART_APP**“.**App art”
HEAP_GL_DEV”Gfx dev**”**/dev/kgsl-3d0
HEAP_CURSORCursor/dev/ashmem/CursorWindow
HEAP_ASHMEM”Ashmem”/dev/ashmem
HEAP_DALVIK_OTHERHEAP_DALVIK_OTHER_APP_CODE_CACHE”.AppJIT”/memfd:jit-cache
HEAP_DALVIK_OTHER_ZYGOTE_CODE_CACHE”.ZygoteJIT”/memfd:jit-zygote-cache**/dev/ashmem/jit-zygote-cache**
HEAP_DALVIK_OTHER**“**Dalvik Other”[anon:dalvik-
HEAP_DALVIK_OTHER_LINEARALLOC”.LinearAlloc”[anon:dalvik-LinearAlloc
HEAP_DALVIK_OTHER_COMPILER_METADATA”.CompilerMetadata”[anon:dalvik-CompilerMetadata
HEAP_DALVIK_OTHER_APP_CODE_CACHE”.AppJIT”[anon:dalvik-jit-code-cache or [anon:dalvik-data-code-cache
HEAP_DALVIK_OTHER_INDIRECT_REFERENCE_TABLE”.IndirectRef”[anon:dalvik-indirect ref
HEAP_DALVIK_OTHER_ACCOUNTING”.GC
HEAP_DALVIK**“**Dalvik Heap”[anon:dalvik-
HEAP_DALVIK_NORMAL”.Heap”[anon:dalvik-alloc space [anon:dalvik-main space
HEAP_DALVIK_LARGE”.LOS[anon:dalvik-large object space [anon:dalvik-free list large object space
HEAP_DALVIK_NON_MOVING”.NonMoving”[anon:dalvik-non moving space
HEAP_DALVIK_ZYGOTE”.Zygote[anon:dalvik-zygote space
HEAP_UNKNOWN_DEV”Other dev**“**Other “/dev”
HEAP_UNKNOWN”Unknown”Other “[anon:” and no name items
HEAP_UNKNOWN_MAP”Other mmap”Other name length > 0
HEAP_GRAPHICS”EGL mtrack”
HEAP_GL**“**GL mtrack”
HEAP_OTHER_MEMTRACK”Other mtrack”

上表中的第一列即为内存细分的索引值,如下在other和dalvik的细分,以便在meminfo打印出更详细的分类值。

  other_states            dalvik_states​
enum {​
    HEAP_UNKNOWN,  ​
    HEAP_DALVIK,​
    HEAP_NATIVE, //2​
​
    HEAP_DALVIK_OTHER, // 3​
    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, // 19​
​
    // Dalvik extra sections (heap).​
    HEAP_DALVIK_NORMAL,​
    HEAP_DALVIK_LARGE,​
    HEAP_DALVIK_ZYGOTE,​
    HEAP_DALVIK_NON_MOVING, // 23​
​
    // Dalvik other extra sections.​
    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, // 29​
​
    // Boot vdex / app dex / app vdex​
    HEAP_DEX_BOOT_VDEX,​
    HEAP_DEX_APP_DEX,​
    HEAP_DEX_APP_VDEX, // 32​
​
    // App art, boot art.​
    HEAP_ART_APP,​
    HEAP_ART_BOOT,  // 34​
​
    _NUM_HEAP,​
    _NUM_EXCLUSIVE_HEAP = HEAP_OTHER_MEMTRACK+1,  //20​
    _NUM_CORE_HEAP = HEAP_NATIVE+1  // 3​
};

每个smaps的详细数据通过下述9个字段进行拆分。

struct stats_t {​
    int pss;​
    int swappablePss;​
    int rss;​
    int privateDirty;​
    int sharedDirty; ​
    int privateClean;​
    int sharedClean;​
    int swappedOut;​
    int swappedOutPss;​
};

Unknown

根据代码中对smaps的分类,当前meminfo中对内存的统计,匿名映射除了下面这几个字段前缀的可以被统计以外,其他有名的匿名映射和没有名字的vma则被统计到Unknown字段中。

"[anon:dalvik-", "[anon:stack_and_tls:", "[anon:libc_malloc]", ​
"[anon:scudo:", "[anon:GWP-ASan"

有名的匿名映射和不显示名字字段看起来有点绕,通过脚本统计system_server进程中统计在Unknown中的字段如下。

item name                                Pss size​
Total                                    1820 ​
[anon:.bss]                              1022​
[anon:linker_alloc]                      460​
[anon:thread signal stack]               180​
[anon:bionic_alloc_small_objects]        72​
[anon:System property context nodes]     24​
[anon:atexit handlers]                   18​
[anon:cfi shadow]                        16​
no_vma_name                              12​
[anon:bionic_alloc_lob]                  8​
[anon:arc4random data]                   8​
[anon:libwebview reservation]            0​

在发现进程占用内存异常时,通过分析meminfo提供的信息可以初步定位异常内存占用。但是若meminfo中Unknown部分占比较大时,会干扰对进程异常占用内存进行判断和追踪,所以对Unknown内存进行分类聚合展示,详见:Unknown 内存拆解方案

Alloc 内存

在数据面板右侧可以看到对已分配和释放内存的累计记录,包括native和dalvik分配(alloc)和释放(free)内存的统计。

native和dalvik分配和释放的内存分别从mallinfo()和虚拟机获取。

获取 Native 申请的内存:

mallinfo() 能返回通过malloc及其相关方法分配的内存信息,如下结构体。

但是不是所有的内存分配对mallinfo() 都是可见的,推荐使用 malloc_info(3) 。

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) nativeMax*/​
   int fsmblks;   /* Space in freed fastbin blocks (bytes) */​
   int uordblks;  /* Total allocated space (bytes) nativeAllocated*/​
   int fordblks;  /* Total free space (bytes) nativeFree */  ​
   int keepcost;  /* Top-most, releasable space (bytes) */​
};

nativeMax、nativeAllocated和nativeFree分别通过mallinfo来获取。

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;​
}

获取 Java 层申请的内存:会直接去 Art 虚拟机中获取虚拟机已经申请的内存大小。dalvikMax、dalvikFree和dalvikAllocated通过runtime提供。

Runtime runtime = Runtime.getRuntime();​
runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.​
long dalvikMax = runtime.totalMemory() / 1024;​
long dalvikFree = runtime.freeMemory() / 1024;​
long dalvikAllocated = dalvikMax - dalvikFree;​

Total

TOTAL是对每一列的汇总求和,其中Pss列有所不同,Pss列的TOTAL值是Pss Total和SwapPss两列之和,可以在代码中证实。

// frameworks/base/core/java/android/app/ActivityThread.java​
printRow(pw, HEAP_COLUMN, "TOTAL", memInfo.getTotalPss(),​
        memInfo.getTotalPrivateDirty(),​
        memInfo.getTotalPrivateClean(),​
        memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :​
        memInfo.getTotalSwappedOut(), memInfo.getTotalRss(),​
        nativeMax+dalvikMax,​
        nativeAllocated+dalvikAllocated, nativeFree+dalvikFree);​
        ​
//frameworks/base/core/java/android/os/Debug.java​
public int getTotalPss() {​
    return dalvikPss + nativePss + otherPss + getTotalSwappedOutPss();​
}   

另外Heap Size、Heap Alloc、Heap Free是对Native Heap和Dalvik Heap的求和,分别来自mallinfo()和runtime,见上一小节。

App Summary

App Summary部分是对上述细分领域的聚合,提供更直观的统计数据。

Java Heap:(Dalvik Heap 的 Private Dirty 数据) + ( .art mmap 部分的 Private Dirty 和 Private Clean 数据) + getOtherPrivate ( OTHER_ART ) 。这里的 .art 是应用的 dex 文件预编译后的 art 文件,所以也是属于该应用的 JavaHeap。

**Native Heap:**Native Heap 的 Private Dirty 数据。

Code:.so .jar .apk .ttf .dex .oat 等资源加总。

**Stack:**getOtherPrivateDirty ( OTHER_STACK )。

**Graphics:**gl,gfx,egl 的数据加总。

Private Other: Native Heap + Dalvik Heap - 其他的 Private Dirty + Private Clean

System:( Total Pss ) - ( Private Dirty 和 Private Clean 的总和)。主要是系统占用的内存,如共享的字体、图像资源等。

下表是对App Summary汇总数据的来源拆解。

字段Debug.java拆解备注
”Java Heap”getSummaryJavaHeap()dalvikPrivateDirty + getOtherPrivate(OTHER_ART)getOtherPrivate(which) = getOtherPrivateClean(which) + getOtherPrivateDirty(which)
“Native Heap”getSummaryNativeHeap()nativePrivateDirty
”Code”getSummaryCode()getOtherPrivate(OTHER_SO, OTHER_JAR, OTHER_APK, OTHER_TTF, OTHER_DEX, OTHER_OAT, OTHER_DALVIK_OTHER_ZYGOTE_CODE_CACHE, OTHER_DALVIK_OTHER_APP_CODE_CACHE)
“Stack”getSummaryStack()getOtherPrivateDirty(OTHER_STACK)
“Graphics”getSummaryGraphics()getOtherPrivate(OTHER_GL_DEV, OTHER_GRAPHICS, OTHER_GL);
“Private Other”getSummaryPrivateOther()TotalPrivateClean + TotalPrivateDirty - getSummaryJavaHeap() - getSummaryNativeHeap() - getSummaryCode() - getSummaryStack() - getSummaryGraphics();TotalPrivateClean = dalvikPrivateClean + nativePrivateClean + otherPrivateCleanTotalPrivateDirty = dalvikPrivateDirty + nativePrivateDirty + otherPrivateDirty
”System”getSummarySystem()TotalPss - TotalPrivateClean - TotalPrivateDirty
”Total Pss**“**getSummaryTotalPss()dalvikPss + nativePss + otherPss + TotalSwappedOutPss
”Total Rss**“**getTotalRss()dalvikRss + nativeRss + otherRss
”Total Swap**“**getSummaryTotalSwap()dalvikSwappedOutPss + nativeSwappedOutPss + otherSwappedOutPss

**三、**Graphics memory

namedescription
EGL mtrack这个部分是gralloc的内存使用。主要是SurfaceView和TextureView的总和。它也包括了帧缓冲区,因此大小也会取决于framebuffers的尺寸。支持的屏幕分辨率越高,EGL mtrack的数目越高。在这个测试中,帧缓冲区的分辨率被降低了来确保比较好的性能。降低帧缓存的大小也会降低这些缓存需要的内存量。
GL mtrack&Gfx devGL和Gfx是驱动反馈的GPU内存,主要是GL纹理大小的总和,GL命令缓冲区,固定的全局驱动RAM消耗以及Shader。注意:客户空间驱动和内核空间驱动共享同一个内存空间。在某些Android版本上,这个部分会被重复计算两次,因此Gfx dev要比实际上使用的数值更大。

Gfx dev

Gfx dev的值是从/sys/class/kgsl/kgsl/proc//gpumem_mapped下读取的,这里主要为/dev/kgsl-3d0占用的空间,gpu使用的内存。smaps中所有kgsl-3d0内存加起来等于gpumem_mapped中读到的内存。

EGL mtrack

主要是APP eglWindowSurface,包括normal HWUI based app windowsurface,SurfaceView,TextureView。这部分内存是从SurfaceFlinger 分配的,可以跟 dumpsys SurfaceFlingher 的 Alloated buffers 可以对应上。

static ssize_t​
imported_mem_show(struct kgsl_process_private *priv,​
                                int type, char *buf)​
{​
    struct kgsl_mem_entry *entry;​
    uint64_t imported_mem = 0;​
    int id = 0;​
    struct deferred_work *work = kzalloc(sizeof(struct deferred_work),​
            GFP_KERNEL);​
​
    if (!work)​
        return -ENOMEM;​
​
    /*​
     * Take a process refcount here and put it back in a deferred manner.​
     * This is to avoid a deadlock where we put back last reference of the​
     * process private (via kgsl_mem_entry_put) here and end up trying to​
     * remove sysfs kobject while we are still in the middle of reading one​
     * of the sysfs files.​
     */​
    if (!kgsl_process_private_get(priv)) {​
        kfree(work);​
        return -ENOENT;​
    }​
​
    work->private = priv;​
    INIT_WORK(&work->work, process_private_deferred_put);​
​
    spin_lock(&priv->mem_lock);​
    for (entry = idr_get_next(&priv->mem_idr, &id); entry;​
        id++, entry = idr_get_next(&priv->mem_idr, &id)) {​
​
        int egl_surface_count = 0, egl_image_count = 0;​
        struct kgsl_memdesc *m;​
​
        if (!kgsl_mem_entry_get(entry))​
                continue;​
        spin_unlock(&priv->mem_lock);​
​
        m = &entry->memdesc;​
        ​
        // 如果是ION内存​
        if (kgsl_memdesc_usermem_type(m) == KGSL_MEM_ENTRY_ION) {​
            kgsl_get_egl_counts(entry, &egl_surface_count,​
                        &egl_image_count); ​
            ​
            // 如果是egl_surface​
            if (kgsl_memdesc_get_memtype(m) ==​
                                KGSL_MEMTYPE_EGL_SURFACE)​
                imported_mem += m->size;​
            else if (egl_surface_count == 0) {​
                uint64_t size = m->size;​
​
                // 如果在多个进程中使用那么计算内存时会是1/N​
                do_div(size, (egl_image_count ?​
                                        egl_image_count : 1));​
                imported_mem += size;​
            }​
        }​
​
        kgsl_mem_entry_put_deferred(entry);​
        spin_lock(&priv->mem_lock);​
    }​
    spin_unlock(&priv->mem_lock);​
​
    queue_work(kgsl_driver.lockless_workqueue, &work->work);​
​
    return scnprintf(buf, PAGE_SIZE, "%llu\n", imported_mem);​
}​

Memory Tracker

Memory Tracker也称memtrack,是一个hal层的库,不同平台库的名称不同,实现方式也有差异。Memory Tracker 主要目标是能够跟踪以任何其他方式无法跟踪的内存,例如由进程分配但未映射到进程地址空间的纹理内存。

第二个目标是能够将进程使用的内存分类为GL,graphics等。所有的内存大小应该在实际的内存使用情况下,考虑到stride,bit depth,page size等。

EGL mtrack/GL mtrack这两项的数据就是通过memtrack获取的。

通过memtrack分别获取graphics、gl和other的内存占用。

ssize_t pss = memtrack_proc_graphics_pss(p);​
pss = memtrack_proc_gl_pss(p);​
pss = memtrack_proc_other_pss(p);​
​
ssize_t memtrack_proc_graphics_pss(memtrack_proc *p)​
{​
    std::vector<MemtrackType> types = { MemtrackType::GRAPHICS };​
    return memtrack_proc_sum(p, types,​
            (uint32_t)MemtrackRecord::FLAG_SMAPS_UNACCOUNTED);​
}

通过类型统计, other包含OTHER 、MULTIMEDIA 和CAMERA。

enum class MemtrackType : int32_t {​
  OTHER = 0,​
  GL = 1,  // GL​
  GRAPHICS = 2,  // GRAPHICS​
  MULTIMEDIA = 3,​
  CAMERA = 4,​
};​

参考文献:

https://www.jianshu.com/p/9edfe9d5eb34

http://kernel.meizu.com/zram-introduction.html

https://juejin.cn/post/6855696848011657224

https://www.cnblogs.com/sevenyuan/p/13305420.html