Android低内存配置
官方原文:
https://source.android.com/devices/tech/perf/low-ram?hl=en
该文档以Android4.4 为例,下面内容不再做说明,翻译出此文档,旨在参考,以优化使用Android系统。
概要
android支持设备使用512M低内存。有以下优化点在系统中使用
平台优化
改善内存管理
- 节省内存配置:Swap to ZRAM
- 杀掉占用很大内存的缓存进程
- 不允许一个大的后台服务常驻在A类型service中
- 杀掉占用太多内存且未被使用的进程,比如输入法,确保系统维护足够的内存
- 对后台服务的启动进行序列化
- 优化了低内存设备的内存使用方式:采用更严格的内存不足 (OOM) 调整级别、缩减图形缓存大小,等等
裁剪系统占用内存
- 裁剪system_server和systemUI(节省几兆内存)
- 预加载dex到Dalvik(节几兆内存)
- 关闭JIT(每个进程节省1.5M)
- 减少各进程的字体缓存
- 使用ArrayMap/ArraySet代替HashMap/HashSet
内存使用统计
在开发应用中增加一些开发选项,显示应用内存状态和统计应用内存使用频率和内存消耗程度。
API
应用中使用ActivityManager.isLowRamDevice(),确保在运行时检测到地内存和关闭占用大量内存的特性。
内存使用跟踪
使用dumpsys meminfo,分类归总内存信息。其中free memory包含缓存进程占用的内存。
编译配置
打开低内存配置
配置
PRODUCT_PROPERTY_OVERRIDES += ro.config.low_ram=true
确保ActivityManager.isLowRamDevice()返回为true
启动器配置
请务必确保启动器的默认壁纸设置未使用动态壁纸。低内存设备不应预装任何动态壁纸。
内核(kernel)配置
减少kernel或者应用直接触发前台内存回收再利用
当kernel使用了所有可用内存,一个进程或者kernel申请一个内存页时,会触发内存回收再利用。这时,内存申请被阻塞,直到释放足够内存。内存回收再利用会把缓存的内存信息写在存储器(如emmc)中或者触发lowmemorykiller杀掉一些进程。这样做的结果是阻塞I/O和UI线程。前台
为了避免直接出发内存回收再利用,内核可配置为触发kswapd或者后台内存回收再利用,确保下次申请内存时有足够的内存分配。
默认的后台内存回收再利用阈值很低,在2G的设备上阈值时2M,512内存设备上阈值是636KB,内存也仅仅可以回收很少的内存。意味着任何进程申请内存的速度快于内存回收的速度,都会触发前台内存回收再利用。
为了支持可配置,在android-3.4分支,patch 92189d47f66c67e5fd92eafaa287e153197a454f (增加额外内存回收配置)。合入该patch,默认ActivityManager 可以告知内核试着拥有3 full-screen 32 bpp buffers的内存。
// Ask the kernel to try to keep enough memory free to allocate 3 full
// screen 32bpp buffers without entering direct reclaim.
int reserve = displayWidth * displayHeight * 4 * 3 / 1024;
int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust);
int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute);
...
SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
该阈值在framework的config.xml中配置
<!-- 配置/proc/sys/vm/extra_free_kbytes ,较大的值确保有大量的内存free,避免OOM。该值根据屏幕尺寸确定,0表示不开启该功能,内存不会预准备内存;-1表示使用内核默认值。 -->
<integer name="config_extraFreeKbytesAbsolute">-1</integer>
<!-- 配置 /proc/sys/vm/extra_free_kbytes. 0 使用默认值. 大于0的值,增加kernel预准备的内存大小。 小于0的值,允许内存使用更多的内存,但是会触发前台内存回收再利用。根据设备显示尺寸设置增加的预准备内存。-->
<integer name="config_extraFreeKbytesAdjust">0</integer>
生效方式
$ grep -nr "sys.sysctl.extra_free_kbytes"
frameworks/base/services/core/java/com/android/server/am/ProcessList.java:297: SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve));
system/core/rootdir/init.rc:664:on property:sys.sysctl.extra_free_kbytes=*
system/core/rootdir/init.rc:665: write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}
打开低内存杀进程功能
配置LowMemoryKiller 的阈值根据内存使用情况可变,当内存多次处于不足时,会增加内核中预准备内存值。当内存需求量不大时,会降低该阀值。
<!-- 设置内核中lowmemorykiller最小释放内存值。一个比较大的值,会让lowmemorykiller很早开始回收内存,使内存中只有很少的进程,和减少I/O的抖动。如果设置太低,会导致I/O不稳定。根据总内存和设备显示大小设置该值,该值会增加或减小lowmemorykiller 回收的内存量。-1使用默认值。 -->
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<!-- 设置自适应 lowmemorykiller最小释放内存数。同上一个。0使用默认值-->
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>
使用Swap to zRAM功能
zRAM通过压缩内存页,然后放置在动态申请的交换内存区,增加内存的可用量。
由于zRAM会导致内存中交换,增加cpu占用,谨慎评估使用zRAM对系统性能的影响。系统处理swap to zRAM 有以下几个级别:
- 使用Swap to zRAM,内核必须打开以下功能
- CONFIG_SWAP
- CONFIG_CGROUP_MEM_RES_CTLR
- CONFIG_CGROUP_MEM_RES_CTLR_SWAP
- CONFIG_ZRAM
- 在分区表增加以下内容
/dev/block/zram0 none swap defaults zramsize=<size in bytes>,swapprio=<swap partition priority>
- zramsize 设置多少字节的未压缩内存被zram区域管理。压缩比例一般是30-50%
- swapprio 如果没有多于一个交换区,可以不用设置
为交换区设置selinux权限
/dev/block/zram0 u:object_r:swap_block_device:s0
- 默认交换区有8个内存页。使用ZRAM,在内存压力大时,同一时间读取一个内存页时增加的性能消耗时微不足道的。同一时间读取一个内存页,init.rc中设置如下
write /proc/sys/vm/page-cluster 0
- 在init.rc中,在mount_all /fstab.X之后加上
swapon_all /fstab.X
- cgroups 在kernel中配置打开,开机时会自动生效
- 如果cgroups 可用,ActivityManager 标记一个低优先级线程作为内存交换。如果的确需要内存,kernel迁移内存页到zRAM swap,并把高优先级给到ActivityManager标记的内存页。
使用CMA
在低内存设备,碎片内存需要关注,尤其哪些不能被充分利用。比如一个视频回放内存分配。有多种解决方案把硬件需求的区域影响降低到最低(不太明白什么影响)
如果hardware准许使用非连续内存申请,ion system heap 可以申请内存在系统内存,排除内存分割。如果需要分配一段连续或者被配置特定地址的区域,使用CMA申请内存。CMA通过ion,直接简单的使用ion cma heap。
应用优化建议
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
http://android-developers.blogspot.com/2009/02/track-memory-allocations.html
http://tools.android.com/recent/lintperformancechecks
- 删除asset下无用的文件(development/tools/findunused可以帮助)
- 使用PNG,特别是使用透明区域时
- native代码,使用calloc()而不是malloc/memset
- 不要使用序列化数据,写到磁盘或读出来
- 不要订阅安装的每个软件包,而是使用ssp过滤。添加如下所示的过滤
<data android:scheme="package" android:ssp="com.android.pkg1" />
<data android:scheme="package" android:ssp="com.myapp.act1" />
理解多种进程状态
- SERVICE - service_restarting
应用程序由于自己的原因使自己在后台运行。最常见的问题应用程序在后台运行时太多了。%duration * pss可能是一个很好的“坏”度量标准,虽然这个集合非常集中,只是做持续时间百分比可能更好地关注我们根本不想让它们运行的事实。 - IMPORTANT_FOREGROUND - RECEIVER
应用程序由于任何原因在后台运行(不直接与用户交互)。这些都为系统增加了内存负载。在这种情况下,(%duration * pss)badness值可能是这些进程的最佳排序,因为其中许多将始终运行的原因很充分,因此它们的pss大小作为其内存负载的一部分非常重要。 - PERSISTENT
持久存在的进程。跟踪pss以观察这些进程的内存变大。 - TOP
用户当前正在与之交互的流程。同样,pss是这里的重要指标,显示应用程序在使用时创建的内存负载量。 - HOME - CACHED_EMPTY
那些被系统缓存起来,可能还会用的进程,可以被随时释放或者重新启动。这是我们估算进程状态的基础,内存状态有:正常、节制的、地内存、严峻的,不同状态决定了可以缓存进程的数量。根据pss,这些进程获取的内存尽可能的小,以使更多的进程可以被缓存。通常,我们会有一个有意义的变化,处于改状态比TOP状态有更小的PSS。 - TOP vs. CACHED_ACTIVITY-CACHED_ACTIVITY_CLIENT
在TOP、CACHED_EMPTY,观察处于后台的进程如何释放内存,确保在后台使用比较少的内存。
分析
分析应用启动时间
使用adb shell am start,加上-P或者–start-profiler选项,启动app的同时启动分析器。该命令在虚拟机fork app 进程后立即启动分析器,在任何代码加载之前。
使用dumpsys分析
如batterystats, netstats, procstats, and usagestats等
分析常驻进程
重启系统,记录进程状态
运行几个小时后,再次记录进程状态,对比数据分析,此时不应该有长时间运行的进程
压力测试
长时间运行设备测试,监控内存变化。创建不同场景,压力测试分析变化