点赞关注,不再迷路,你的支持对我意义重大!
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. 内存分析工具
参考资料
创作不易,你的「三连」是丑丑最大的动力,我们下次见!