序
内存优化一直是一个很重要但却缺乏关注的点,内存作为程序运行最重要的资源之一,需要运行过程中做到合理的资源分配与回收,不合理的内存占用轻则使得用户应用程序运行卡顿、ANR、黑屏,重则导致用户应用程序发生 OOM(out of memory)崩溃。在你认真跟踪下来可能会发现内存出现问题的地方仅仅只是一个表现的地方,并非深层次的原因,因为内存问题相对比较复杂,它是一个逐渐挤压的过程,正好在你出现问题的代码那里爆了,所以针对应用的内存问题开发者必须多加关注。
在内存优化开始分析之前,我们需要先搞清楚应用当前占用了多少内存,分别是什么,它们的概念指的是什么,另外,我们使用什么工具和方法进行分析,然后再进行相应的优化,我们需要解决什么问题,比如内存抖动、内存泄露、频繁GC、锯齿状、内存溢出等。
1. 内存概念理解与指标的选择
1.1 Top命令中的 VIRT / RES / SHR 含义
名词解释,Swap out:swap是磁盘上一块存储空间。当系统内存使用超过一定值的时候,操作系统就会启动内核进程kswapd,kswapd将部分内存数据置换到swap(swap out),从而释放一部分内存出来,当进程需要读取被置换到swap的页的时候,内核再将数据从swap读到内存(swap in)。
1.2 smaps 中的 RSS / PSS
通过 top 命令我们已经能看出进程的虚拟空间大小(VIRT)、占用的物理内存(RES)以及和其他进程共享的内存(SHR)。但是仅此而已,如果我想知道如下问题:
- 进程的虚拟内存空间的分布情况,比如 heap 占用了多少空间、文件映射(mmap)占用了多少空间、stack 占用了多少空间?
- 进程是否有被交换到 swap 空间的内存,如果有,被交换出去的大小?
- mmap 方式打开的数据文件有多少页在内存中是脏页(dirty page)没有被写回到磁盘的?
- mmap 方式打开的数据文件当前有多少页面已经在内存中,有多少页面还在磁盘中没有加载到 page cahe 中?
以上这些问题都无法通过 top 命令给出答案,感兴趣的朋友可以在cnaaa服务器上部署环境自己试一下,但是有时候这些问题正是我们在对程序进行性能瓶颈分析和优化时所需要回答的问题。所幸的是,世界上解决问题的方法总比问题本身要多得多。linux 通过 proc 文件系统为每个进程都提供了一个 smaps 文件,通过分析该文件我们就可以一一回答以上提出的问题。
在 smaps 文件中,每一条记录(如下图所示)表示进程虚拟内存空间中一块连续的区域。其中第一行从左到右依次表示地址范围、权限标识、映射文件偏移、设备号、inode、文件路径。
名词 | 含义 |
Size | 表示该映射区域在虚拟内存空间中的大小 |
Rss | 表示该映射区域当前在物理内存中占用了多少空间 |
Shared_Clean | 和其他进程共享的未被改写的 page 的大小 |
Shared_Dirty | 和其他进程共享的被改写的 page 的大小 |
Private_Clean | 未被改写的私有页面的大小。 |
Private_Dirty | 已被改写的私有页面的大小。 |
Swap | 表示非 mmap 内存(也叫 anonymous memory,比如 malloc 动态分配出来的内存)由于物理内存不足被 swap 到交换空间的大小。 |
Pss | 该虚拟内存区域平摊计算后使用的物理内存大小 (有些内存会和其他进程共享,例如 mmap 进来的)。比如该区域所映射的物理内存部分同时也被另一个进程映射了,且该部分物理内存的大小为 1000KB,那么该进程分摊其中一半的内存,即 Pss=500KB。 |
1.3 VSS/RSS/PSS/USS
名词 | 含义 | 详细信息 |
VSS | virtual set size | 虚拟耗用内存(包含共享库占用的内存) VSS是一个进程的总的大小。只有当进程执行且整个进程都驻存到物理内存时才RSS=VSS 。 |
RSS | Resident set size | 实际使用物理内存(包含共享库占用的内存) RSS是进程实际驻存在物理内存的部分的大小。因为一个进程执行不需要把整个进程都全部驻存到物理内存。RSS是最常用的内存指标,表示进程占用的物理内存大小。但是,将各进程的RSS值相加,通常会超出整个系统的内存消耗,这是因为RSS中每个进程都包含了各进程间共享的内存,因此存在重叠部分。 |
PSS | Proportional set size | 实际使用的物理内存(比例分配共享库占用的内存) 与RSS相比,PSS会更准确一些,它将共享内存的大小进行平均后,再分摊到各进程上去。 |
USS | Unique set size | 进程独自占用的物理内存(不包含共享库占用的内存) USS则是PSS中自己的部分,它只计算了进程独自占用的内存大小,不包含任何共享的部分。 |
一般来说内存占用大小有如下规律:VSS >= RSS >= PSS >= USS
与上述Top命令中的取值对比,可得出如下结论:
VIRT = VSS
RES = RSS
RES - SHR = USS
1.4 选择合适指标
在我们的项目中,我们统计内存占用情况,需要一个指标,综合考虑,使用 PSS 或 USS 是比较好的选择,可以反映出真实情况;
在实际使用中,发现可以通过“dumpsys meminfo”命令导出PSS内存,所以就以此为指标进行实际的数据统计。
2. 统计脚本
指标选定了,如果我们想看某一时刻的内存占用,直接一条命令就搞定了,但如果我们要是去分析这个占用,就需要时刻的监测,此时,就需要我们进行多次取值,然后取平均值了,所以是时候整一个简单的脚进行去做这个事情了。
2.1 原理
- 用shell写一个循环,不断的dump出值,然后写入文件中;
- 可以从最终的文件中将所有的值进行平均,得到稍微准确一些的数据;
- 参数目前是在脚本中指定的,包括包名、输出文件名、循环时间和次数,后续有需要可改成参数传入;
2.2 脚本
bash 代码解读复制代码#!/bin/bash
# 指定要监控的进程名
PROCESS1="com.***"
# CSV文件路径
CSV_FILE="memory_usage.csv"
# 清除或创建CSV文件
> "$CSV_FILE"
# 写入CSV文件头
echo "Time,${PROCESS1}_PSS" >> "$CSV_FILE"
# 多次循环,每秒执行一次
for i in {1..60}
do
# 获取当前时间
TIME=$(date +%Y-%m-%d\ %H:%M:%S)
# 使用adb shell dumpsys meminfo获取进程内存信息
# 注意:这里使用了grep和awk来提取RSS和PSS值,具体字段可能因Android版本而异
PSS1=$(adb shell dumpsys meminfo $PROCESS1 | grep "TOTAL:" | awk '{print $2}')
# 将结果写入CSV文件
echo "$TIME,$PSS1" >> "$CSV_FILE"
echo "$i, MEM=$PSS1"
# 等待一秒
sleep 1
done
3. 通过AS中的 Profiler工具分析内存占用情况
3.1 空工程内存占用情况
- 截图中总内存占用25.3 M,与脚本取出来的内存内用平均值(25706KB),数据相吻合;
- 各类型内存分布情况:Code9.8M, other 5.3M, Java 5.4M,Native4.7M;
3.2 我们的App
- 总内存:150.3M
- 各类型内存分布情况:Code 64.7M,Native 28.7M,Java 26.7M,Others 30.2M;
- Code占比是大头,占了64.7M,占比 43%;
3.3 分析
App越大,Code占用的内存越多,我们的App大约占用58.7 - 64.8M,可通过优化App体积或其它Dex优化或加载的方案进行此块的内存占用优化;或通过优化dex文件排列,或按需加载的方法,达到不使用的功能暂不加载的目的,以减小内存占用。
4. 总结
本篇文章梳理了内存相关的几个概念,总结出了适合进行内存占用分析的内存指标,并写了一个脚本自动化的获取自己app的平均内存,提高了内存分析的效率。
5. 团队介绍
「三翼鸟数字化技术平台-定制平台开发」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。
评论记录:
回复评论: