Android | 内存指标与分析方法

点赞关注,不再迷路,你的支持对我意义重大!

Hi,我是丑丑。本文 「Android 路线」| 导读 —— 从零到无穷大 已收录,这里有 Android 进阶成长路线笔记 & 博客,欢迎跟着彭丑丑一起成长。(联系方式在 GitHub)


目录


前置知识

这篇文章的内容会涉及以下前置 / 相关知识,贴心的我都帮你准备好了,请享用~

  • 程序执行: Android 虚拟机 | 从类加载到程序执行

这篇文章偏底层,难免有写错的地方还请你多多斧正哦~


1. 内存类型

Android 系统包括三种不同类型的内存:RAM、zRAM 和 ROM:

  • RAM: 相当于 PC 中的内存条,是暂存 App 临时数据的存储介质。RAM 越大手机就能运行更多程序,且更佳流畅。考虑到体积和功耗,手机 RAM 不会使用 PC 中的 DDR RAM ,而是采用 LPDDR RAM(低功耗双倍数据速率内存);

  • zRAM: 相当于一块 虚拟内存,其基本原理是在 RAM 中划分一篇区域作为 SWAP 的交换分区,由于 zRAM 对存放的内容会进行实时的压缩,从而可以让系统当作虚拟内存来用。传统的虚拟内存是存储在磁盘上的,而 zRAM 是存储在内存中的,访问速度会提高很多;

  • ROM: 相当于 PC 中的磁盘,是持久化存储数据的存储介质。ROM 越大手机能存储更多数据。


2. 内存分页

对于内核来说,无论是内核进程还是用户进程,说到底都是task_struct结构体的一个实例。task_struct 也叫进程描述符(process descriptor),里面记录了进程相关的所有信息。

在 task_struct 中有一个mm_struct的数据结构,也叫内存描述符(memory descriptor),里面记录了 Linux 进程内存管理的所有信息。mm_struct 定义在linux/mm_types.h头文件中,其中有一个页(page)的数据结构:

struct page {
       page_flags_t flags;            标志位(每一位单独表示一种状态)
       atomic_t _count;               引用计数(-1 表示未被使用)
       atomic_t _mapcount;            映射计数
       unsigned long private;         私有数据指针
       struct address_space *mapping; 该页所在地址空间描述结构指针,用于内容为文件的页帧
       pgoff_t index;                 该页描述结构在地址空间 radix 树 page_tree 中的对象索引号即页号
       struct list_head lru;          最近最久未使用 struct slab 结构指针链表头变量
       void *virtual;                 页在虚拟地址中的地址,对于不能映射到内核空间的内存,该值为 NULL
};

—— 图片引用自网络

页(Page)是 Linux 内核进行内存管理的基本单位,通常一个页的大小为 4 KB。根据页面是否使用分为 “可用页” 和 “已使用页” 两种,其中已使用页可以分为以下类别:

2.1 缓存页

缓存页是指有存储器中的文件支持的内存,分为两种:私有页 & 共享页

  • 私有页: 一个进程独占

    • 干净页: 存储器中未经修改的文件副本,可由kswapd删除以增加可用内存;
    • 脏页: 存储器中经过修改的文件副本,可由kswapd移动到 zRAM 或者在 zRAM 中压缩以增加可用内存。
  • 共享页: 多个进程共享

    • 干净页: 存储器中未经修改的文件副本,可由kswapd删除以增加可用内存;
    • 脏页: 存储器中经过修改的文件副本,允许通过kswapd或者通过msync()munmap()将更改写会存储器中的文件,以增加可用空间。

2.2 匿名页

匿名页是没有存储器中的文件支持的内存(例如由设置了MAP_ANONYMOUS标志的mmap()进行分配)

  • 脏页: 可由kswapd移动到 zRAM 或者在 zRAM 中压缩以增加可用内存。

3. 最大堆内存

为了避免应用滥用内存,Android 系统会限制应用可以申请的最大堆内存,超过此限制就会抛出 OOM 异常。Android 设备出厂后,最大堆内存就已经确定,相关的配置位于系统根目录/system/build.prop文件中,我们可以通过命令查看:

命令:
adb shell cat /system/build.prop
------------------------------------------------------------------
输出:
...
dalvik.vm.heapstartsize=16m           [进程启动的初始堆内存]
dalvik.vm.heapgrowthlimit=128m        [进程最大堆内存]
dalvik.vm.heapsize=192m               [进程最大堆内存(开启 largeHeap="true")]
dalvik.vm.heaptargetutilization=0.75
dalvik.vm.heapminfree=512k
dalvik.vm.heapmaxfree=8m
...
虚拟机参数 描述
dalvik.vm.heapstartsize 进程启动的初始堆内存
dalvik.vm.heapgrowthlimit 进程最大堆内存
dalvik.vm.heapsize 进程最大堆内存(开启 largeHeap="true")
dalvik.vm.heaptargetutilization
dalvik.vm.heapminfree
dalvik.vm.heapmaxfree

在 App 虚拟机启动时,会读取/system/build.prop文件的配置,源码位于:AndroidRuntime.cpp

-> 虚拟机启动
int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv, bool zygote) {
    ...
    parseRuntimeOption("dalvik.vm.heapstartsize", heapstartsizeOptsBuf, "-Xms", "4m");
    parseRuntimeOption("dalvik.vm.heapsize", heapsizeOptsBuf, "-Xmx", "16m");
    parseRuntimeOption("dalvik.vm.heapgrowthlimit", heapgrowthlimitOptsBuf, "-XX:HeapGrowthLimit=");
    parseRuntimeOption("dalvik.vm.heapminfree", heapminfreeOptsBuf, "-XX:HeapMinFree=");
    parseRuntimeOption("dalvik.vm.heapmaxfree", heapmaxfreeOptsBuf, "-XX:HeapMaxFree=");
    parseRuntimeOption("dalvik.vm.heaptargetutilization", heaptargetutilizationOptsBuf, "-XX:HeapTargetUtilization=");
    ...
}

需要注意的是,配置dalvik.vm.heapgrowthlimit限制的仅仅是 Java 堆内存,本地内存不受其限制的。换句话说,应用可以使用的最大内存其实是可以大于最大堆内存的。


4. 进程到底占用了多少内存?

在确定进程占用了多少内存时,必须考虑多个进程共享页的情况。在 Linux 里,一个进程占用的内存有四种指标,分别是:

内存指标 全称 描述
VSS Virtual Set Size 一个进程可访问的地址空间
RSS Resident Set Size 常驻内存大小(包含共享页内存)
PSS Proportional Set Size 按比例分摊的内存大小(按比例分摊共享页内存)
USS Unique Set Size 独占内存大小(不包含共享页的内存)

一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS。

—— 图片引用自 https://developer.android.google.cn/topic/performance/memory-management Android Developers

  • VSS: 一个进程可访问的地址空间,其大小还包括分配但未使用的内存,因此对于分析进程占用内存用处不大;

  • RSS: 一个进程常驻内存大小,包含了进程使用的私有页和共享页,相对于 PSS 计算更快,更加适合跟踪内存分配量的变化。需要注意所有进程的 RSS 相加会超过物理内存很多;

  • PSS: 进程按比例分摊的内存大小,相对于 RSS,它是按照比例分摊共享页占用的内存的。例如一个共享页被 N 个进程共享,那么 PSS 只会计算 共享页内存。PSS 在有些时候也是有偏差的,例如进程 A 和进程 B 使用同一个共享页没并且 B 在 A 之后启动,那么在 B 启动的那一刻,A 的 PSS 会突然断崖式降低,即时 A 并没有做任何内存释放,这会给分析进程内存行为带来麻烦。

  • USS: 一个进程占用的私有内存大小,相对于其它指标,USS 不包含共享页,只包含进程独占的内存部分。如果该进程终止,USS 就是实际被返还给系统的内存大小。

—— 图片引用自 https://www.cnblogs.com/sunsky303/p/13494977.html —— sunsky303 著


5. 内存分析命令

关于输出信息的具体分析,建议直接看 Gityuan 的这篇文章:《Android 内存分析命令》,已经写得非常详细了。

命令 作用
dumpsys meminfo 查看进程的内存使用情况
procrank 查看进程的内存使用情况(输出更详细的 VSS / RSS / PSS / USS 内存指标)
cat/proc/meminfo 查看更加详细的内存信息
free 查看可用内存
showmap 查看虚拟地址区域的内存情况
vmstat 查看内存情况,还可以查看进程运行队列、系统切换、CPU 时间占比等情况

6. 内存分析工具


参考资料


创作不易,你的「三连」是丑丑最大的动力,我们下次见!

你可能感兴趣的:(Android | 内存指标与分析方法)