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 |
*.so | HEAP_SO |
*.jar | HEAP_JAR |
*.apk | HEAP_APK |
*.ttf | HEAP_TTF |
*.odex (*.dex) | HEAP_DEX ----HEAP_DEX_APP_DEX |
*.vdex (@boot /boot /apex) | HEAP_DEX ----HEAP_DEX_BOOT_VDEX ----HEAP_DEX_APP_VDEX |
*.oat | HEAP_OAT |
*.art *.art] (@boot /boot /apex) | HEAP_ART ----HEAP_ART_BOOT ----HEAP_ART_APP |
/dev/ | HEAP_UNKNOWN_DEV |
/dev/kgsl-3d0 | HEAP_GL_DEV |
/dev/ashmem/CursorWindow | HEAP_CURSOR |
/dev/ashmem/jit-zygote-cache /memfd:jit-cache /memfd:jit-zygote-cache | HEAP_DALVIK_OTHER |
/dev/ashmem | HEAP_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 | 全称 | 含义 | 等价 |
---|---|---|---|
USS | Unique Set Size | 物理内存 | 进程独占的内存 |
PSS | Proportional Set Size | 物理内存 | Pss =Uss+按比例包含共享库 |
RSS | Resient Set Size | 物理内存 | RSS=USS+包含共享库 |
VSS | Virtual 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 主要属性值价绍
我们先从横坐标开始:
序号 | 字段名称 | 解释 |
---|---|---|
1 | Pss Total | Pss是指实际使用的物理内存,考虑了在进程之间共享RAM页的情况。进程独占的RAM页会直接计入其PSS值,而与其他进程共享的RAM页则会按相应比例计入PSS值。例如,两个进程之间共享的RAM页会将其一半的大小 分别计入这两个进程的PSS中, Pss Total 就是指某一项Pss的总值。 |
2 | Private Dirty | 进程私有,是仅分配给应用堆的实际RAM,包含了您自己的分配和zygote分配页,这些分配页自从zygote派生您的应用进程以来已被修改。 |
3 | Private Clean | 进程私有的,相对磁盘数据没有使用修改的内存。 |
4 | SwapPss Dirty | 一些Android设备确实使用了内存交换,但它们使用的是内存而不是闪存。Linux有一个称为ZRAM的特性,它可以压缩页面,然后将它们交换到一个特殊的RAM区域,并在需要时再次解压它们。因此,“交换肮脏”中列出的页面很可能是在ZRAM中。 |
纵坐标
序号 | 属性名 | 说明 |
---|---|---|
1 | Native Heap | 在Native Code中使用malloc分配出的内存 |
2 | Dalvik Heap | Dalvik 虚拟机(Java 代码)分配的空间,不包括它自身的开销,Dalvik堆中和zygote进程共享的部分算是sharedDirty |
3 | Dalvik Other | 类数据结构和索引占据的内存 |
3 | Stack | 堆内存 |
4 | Ashmem | 匿名共享内存,此类内存与cache shrinker关联,可以控制cache shrinker在适当时机回收这些共享内存 |
5 | Other dev | 内存drvier占用的内存 |
6 | .so mmap | 映射的.so(native) 占用的内存 |
7 | .jar mmap | Java文件代码占用内存 |
8 | .apk mmap | apk代码占用内存 |
8 | .dex mmap | 映射的.dex(Dalvik 或ART)代码占用的内存 |
9 | .oat mmap | 代码映射占用的RAM量。此映像在所有应用之间共享,不受特定应用影响 |
10 | .art mmap | 堆映像占用的RAM量,此映像在所有应用之间共享,不受特定应用影响。尽管ART映射包含Object实例,它仍然不会计入您的堆大小 |
11 | other mmap | 其他文件占用的内存 |
- 我们某个进程实际的Pss 代码实际内存大小是指横坐标为Pss Total,纵列为Total值(其值等于Pss Total 列+SwapPss Dirty 列的总和),如上所示微信总内存为
206365
K. - .art mmap 和.dex mmap 列的详细信息可以通过
showmap pid
(其他是通过解析/proc/pid/smaps
来得到的结果,详细见待整理)来获取更详细的信息,meminfo里面的信息其实也是smaps通过后缀名来统计各类的mmap值。
3.3.2 Heap相关
Heap Size | Heap Alloc | Heap Free | |
---|---|---|---|
Native Heap | Native 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 详解的主要内容,如果未能解决你的问题,请参考以下文章
Android 内存优化dumpsys meminfo PID 查看单进程内存信息详解