原文:https://source.android.com/devices/tech/perf/low-ram
介绍
Android现在支持512MB RAM的设备。本文旨在帮助OEM为低内存设备优化和配置Android 4.4。其中一些优化非常通用,它们也可以应用于以前的版本。
Android 4.4平台优化
改进了内存管理
- 验证的内存节省内核配置:交换到ZRAM。
- 如果要缓存且太大,则终止缓存进程。
- 不允许大型服务将自己放回A服务中(因此它们不会导致启动器被终止)。
- 终止在空闲维护中过大的进程(即使是通常不可用的,例如当前的IME)。
- 序列化后台服务的启动。
- 调整低RAM设备的内存使用:更严格的内存不足(OOM)调整级别,更小的图形缓存等。
减少系统内存
- 裁剪system_server和SystemUI进程(节省几MB)。
- 在Dalvik中预加载dex缓存(节省几MB)。
- 验证JIT-off选项(每个进程最多可节省1.5MB)。
- 减少每进程字体缓存开销。
- 引入ArrayMap / ArraySet并在Framework中广泛使用,作为HashMap / HashSet的轻量级替代品。
进程统计
添加新的开发者选项,用于显示内存状态和应用程序内存使用情况,并根据运行频率和内存消耗量进行排序。
API
添加新的ActivityManager.isLowRamDevice(),用于允许应用程序检测何时在低内存设备上运行并选择禁用大RAM功能。
内存跟踪
新memtrack HAL用于跟踪图形内存分配,dumpsys meminfo
中的附加信息,meminfo中的概要说明(例如报告的可用RAM包括缓存进程的RAM,以便OEM不会尝试优化错误的东西)。
构建时配置
启用低Ram设备标志
我们正在引入一个名为ActivityManager.isLowRamDevice()
的新API ,以便应用程序确定是否应该关闭在低内存设备上运行不佳的特定内存密集型功能。
对于512MB设备,此API有望返回true
。它可以通过设备makefile中的以下系统属性启用。
PRODUCT_PROPERTY_OVERRIDES + = ro.config.low_ram = true
Launcher配置
确保Launcher上的默认壁纸设置不 使用动态壁纸。低内存设备不应预安装任何动态壁纸。
内核配置
调整内核/ActivityManager以减少直接回收
当进程或内核尝试分配一页内存(直接或由于新页面中的错误)并且内核已使用所有可用空闲内存时,会发生直接回收。这要求内核在释放页面时阻止分配。相反,这经常需要磁盘I/O清除垃圾文件(备份页面)或等待lowmemorykiller
终止进程。这可能导致任何线程中的额外I/O,包括UI线程。
为避免直接回收,内核具有触发kswapd
或后台回收的水印。这是一个试图释放页面的线程,因此下一次真正的线程分配它可以快速成功。
触发后台回收的默认阈值相当低,2GB设备上约为2MB,512MB设备上为636KB。并且内核回收在后台只回收了几MB的内存。这意味着快速分配超过几兆字节的任何进程都将很快导致直接回收。
android-3.4内核分支中添加了对新内核可调参数的支持,见补丁92189d47f66c67e5fd92eafaa287e153197a454f(“添加额外的空闲kbytes可调”)。将此补丁合入到设备的内核将允许ActivityManager告诉内核尝试保留3个全屏32 bpp内存缓冲区。
可以通过框架config.xml配置这些阈值
<!-- Device configuration setting the /proc/sys/vm/extra_free_kbytes tunable
in the kernel (if it exists). A high value will increase the amount of memory
that the kernel tries to keep free, reducing allocation time and causing the
lowmemorykiller to kill earlier. A low value allows more memory to be used by
processes but may cause more allocations to block waiting on disk I/O or
lowmemorykiller. Overrides the default value chosen by ActivityManager based
on screen size. 0 prevents keeping any extra memory over what the kernel keeps
by default. -1 keeps the default. -->
<integer name="config_extraFreeKbytesAbsolute">-1</integer>
<!-- Device configuration adjusting the /proc/sys/vm/extra_free_kbytes
tunable in the kernel (if it exists). 0 uses the default value chosen by
ActivityManager. A positive value will increase the amount of memory that the
kernel tries to keep free, reducing allocation time and causing the
lowmemorykiller to kill earlier. A negative value allows more memory to be
used by processes but may cause more allocations to block waiting on disk I/O
or lowmemorykiller. Directly added to the default value chosen by
ActivityManager based on screen size. -->
<integer name="config_extraFreeKbytesAdjust">0</integer>
调整LowMemoryKiller
ActivityManager配置LowMemoryKiller的阈值,以匹配在每个优先级存储桶中运行进程所需的文件(支持页面(缓存页面))的工作集的期望。如果设备对工作集具有高要求,例如,如果厂商UI需要更多内存或者如果添加了更多服务,则可以增加阈值。
如果为文件支持的页面保留了太多内存,则可以减少阈值,因此在因缓存变得太小而导致磁盘抖动发生之前后台进程将会被长时间终止。
<!-- Device configuration setting the minfree tunable in the lowmemorykiller
in the kernel. A high value will cause the lowmemorykiller to fire earlier,
keeping more memory in the file cache and preventing I/O thrashing, but
allowing fewer processes to stay in memory. A low value will keep more
processes in memory but may cause thrashing if set too low. Overrides the
default value chosen by ActivityManager based on screen size and total memory
for the largest lowmemorykiller bucket, and scaled proportionally to the
smaller buckets. -1 keeps the default. -->
<integer name="config_lowMemoryKillerMinFreeKbytesAbsolute">-1</integer>
<!-- Device configuration adjusting the minfree tunable in the
lowmemorykiller in the kernel. A high value will cause the lowmemorykiller to
fire earlier, keeping more memory in the file cache and preventing I/O
thrashing, but allowing fewer processes to stay in memory. A low value will
keep more processes in memory but may cause thrashing if set too low. Directly
added to the default value chosen by ActivityManager based on screen
size and total memory for the largest lowmemorykiller bucket, and scaled
proportionally to the smaller buckets. 0 keeps the default. -->
<integer name="config_lowMemoryKillerMinFreeKbytesAdjust">0</integer>
交换到zRAM
zRAM交换可以通过压缩内存页面并将它们放入动态分配的内存交换区域来增加系统中可用的内存量。
同样,由于内存的小幅增加会占用CPU时间,因此您应该仔细测量zRAM交换对系统的性能影响。
Android在几个级别处理交换到zRAM:
- 首先,必须启用以下内核选项才能有效地使用zRAM交换:
CONFIG_SWAP
CONFIG_CGROUP_MEM_RES_CTLR
CONFIG_CGROUP_MEM_RES_CTLR_SWAP
CONFIG_ZRAM
- 然后,您应该在fstab中添加一行如下所示:
/dev/block/zram0 none swap defaults zramsize=<size in bytes>,swapprio=<swap partition priority>
* `zramsize`是必需的,表示您希望zram区域保留多少未压缩的内存。通常观察到30-50%范围内的压缩比。
* `swapprio` 是可选的,如果没有多个交换区域则不需要。
您还应确保在设备特定的[ sepolicy/file_contexts](https://source.android.com/security/selinux/implement.html)中将关联的块设备标记为swap_block_device,以便SELinux正确处理它。
/dev/block/zram0 u:object_r:swap_block_device:s0
- 默认情况下,Linux内核一次交换8页内存。使用ZRAM时,一次读取1页的增量成本可以忽略不计,并且可能有助于设备处于极端内存压力下。要一次只读1页,请将以下内容添加到
init.rc
:
write /proc/sys/vm/page-cluster 0
- 在你
init.rc
的mount_all /fstab.X
行后,添加:
swapon_all /fstab.X
- 如果在内核中启用了该功能,则会在引导时自动配置内存cgroup。
- 如果内存cgroup可用,则ActivityManager会将优先级较低的线程标记为比其他线程更可交换。如果需要内存,Android内核将开始将内存页面迁移到zRAM交换区,为已经由ActivityManager标记的内存页面提供更高的优先级。
Carveouts,Ion和连续内存分配(CMA)
特别重要的是在低内存设备上要注意清理,特别是那些并不总是被充分利用的设备 - 例如用于安全视频播放的分区。有几种解决方案可以根据硬件的具体要求,最大限度地减少削减区域的影响。
如果硬件允许不连续的内存分配,离子系统堆允许从系统内存分配内存,从而无需进行分割。它还试图进行大量分配以消除外围设备上的TLB压力。如果存储区必须是连续的或局限于特定的地址范围,则可以使用连续的存储器分配器(CMA)。
这创建了一个系统也可以用于可移动页面的分割。当需要该区域时,可移动页面将从中移出,允许系统在空闲时将大型分区用于其他目的。使用离子cma堆,CMA可以直接使用或更简单地通过离子使用。
应用优化提示
- 查看管理您的应用内存以及以前相同主题的博文:
- 检查/删除预装应用程序中未使用的assets - development/tools/findunused(应该有助于缩小应用程序)。
- assets中使用PNG格式,尤其是当它们具有透明区域时
- 如果编写本机代码,请使用calloc()而不是malloc/memset
- 不要启用将Parcel数据写入磁盘并稍后读取的代码。
- 不要订阅安装的每个软件包,而是使用ssp过滤。添加如下所示的过滤:
<data android:scheme="package" android:ssp="com.android.pkg1" />
<data android:scheme="package" android:ssp="com.myapp.act1" />
了解Android中的各种流程状态
SERVICE - SERVICE_RESTARTING
应用程序由于自己的原因使自己在后台运行。最常见的问题应用程序在后台运行时太多了。%duration * pss可能是一个很好的“坏”度量标准,虽然这个集合非常集中,只是做持续时间百分比可能更好地关注我们根本不想让它们运行的事实。IMPORTANT_FOREGROUND - RECEIVER
应用程序由于任何原因在后台运行(不直接与用户交互)。这些都为系统增加了内存负载。在这种情况下,(%duration * pss)badness值可能是这些进程的最佳排序,因为其中许多将始终运行的原因很充分,因此它们的pss大小作为其内存负载的一部分非常重要。PERSISTENT
持久性系统流程。跟踪pss以观察这些过程变得过大。TOP
用户当前正在与之交互的流程。同样,pss是这里的重要指标,显示应用程序在使用时创建的内存负载量。HOME - CACHED_EMPTY
所有这些在底部的过程都是系统保留的过程,以防再次需要它们; 但是他们可以随时自由终止并在需要时重新创建。这些是我们计算内存状态的基础 - 正常,中等,低,关键是基于系统可以保留多少这些进程。对于这些过程来说,关键是pss; 这些进程应该在它们处于这种状态时尽可能地减少它们的内存占用,以允许保持最大的进程总数。一般来说,表现良好的应用程序的pss占用空间在此状态下比在TOP时要小得多。TOP与CACHED_ACTIVITY-CACHED_ACTIVITY_CLIENT比较
进程为TOP时的pss与处于这些特定缓存状态之间的pss之间的差异是查看进入后台时释放内存的最佳数据。排除CACHED_EMPTY状态会使这些数据更好,因为除了执行UI之外,由于某些原因它会删除进程启动时的情况,因此不必处理与用户交互时获得的所有UI开销。
分析
分析应用启动时间
在应用启动时,使用$ adb shell am start
命令加上 -P
或 --start-profiler
选项运行探查器。在您的任何代码加载到zygote之前,这将在您的进程从zygote创建后立即启动探查器。
使用bugreports进行分析
包含当前可用于调试的各种信息。这些服务包括batterystats
,netstats
, procstats
,和usagestats
。您可以使用以下行找到它们:
------ CHECKIN BATTERYSTATS (dumpsys batterystats --checkin) ------
7,0,h,-2558644,97,1946288161,3,2,0,340,4183
7,0,h,-2553041,97,1946288161,3,2,0,340,4183
检查任何持久进程
重新启动设备并检查进程。
运行几个小时,然后再次检查进程。不应该有任何长时间运行的进程。
进行长寿测试
运行更长的持续时间并跟踪进程的内存。它增加了吗?它保持不变吗?创建Canonical用例并在这些场景下运行长寿测试。