深入探析 Android Memory

660 阅读6分钟

Android应用开发总是提醒我们, 我们是Android硬件和操作系统的一部分.

我们可以从硬件方面将安卓手机分为若干等级. 基本上, 我们可以将其分为入门 -- 中级和高级硬件 -- API 手机. 在开发应用时, 如果能在低级硬件或 API 上运行, 就意味着能吸引更多用户.

随着时间的推移, 我们往往会在应用中集成越来越多的功能和技术, 这意味着随着时间的推移代码维护量会增加, 对硬件内存等的要求也会提高.

Android操作系统的工作原理是, 未使用或闲置的内存就是浪费的内存. 它总是根据需要以优化的方式使用所有可用空间. 如果有足够的空间, 系统会在应用关闭或放入后台后继续保留内存. 当用户再次打开应用时, 启动速度会更快. 这就是为什么大多数安卓设备在运行时可用空间很少的原因.

软件使用内存需要访问权限. 这通常是通过 Linux 内核和驱动程序实现的. 然后, 应用通过驱动程序提供的接口进行通信. 内存区域可分为虚拟内存和物理内存. CPU 通过内存管理来管理内存.

Android 内存类型

Android 操作系统共享 RAM 页面, 以便在 RAM 中容纳所需的一切. 每个应用程序进程都会fork Zygote进程, 并在系统启动时加载通用框架代码后启动. 这种方法允许框架源代码在大多数 RAM 页面之间共享. 详见

Android设备包括Ram, zRam和存储.

  • Ram: 大小有限. 最快的内存类型.
  • zRam: 用于短期情况的 RAM 部分. 所有内容在移入和移出 zRam 时都会压缩, 复制时会解压缩.
  • 存储: 包含持久数据. 它包含应用程序, 库和平台文件等数据.

如果内存不足, Android操作系统会采取各种预防措施. 让我们考虑一下多个应用在后台运行的情况. Android操作系统会在系统中分阶段激活垃圾收集 -- kswapd 和低内存杀手(LMK).

垃圾回收

基本上, 它有两个重要任务.

  • 查找程序中将来无法访问的对象.
  • 使系统能够恢复无法访问的对象所使用的资源.

操作系统会跟踪每个已分配的内存区域, 并管理其中的操作. ART 和 Dalvik 类似. 根据对象的大小和持有时间的长短, 内存被分为不同的分类, 并对持有时间的长短进行管理.

Oracle JVM 垃圾回收基础知识

在第一阶段, 对象被转移到 Young 阶段, 然后转移到 Old-Permanent 阶段. 每个对象都有内存上限. 当一代内存开始填满时, Android 操作系统会运行垃圾回收. 垃圾回收的运行时间取决于活动对象的数量和大小. 大多数情况下, 垃圾回收由系统管理. 有一些方法可以干扰这一过程, 但不建议使用.

Kswapd(内核交换守护进程)

Android操作系统使用内存映射页. 每个页面大约相当于 4kb. 页面可分为缓存页面, 已用页面和空闲页面. 页面可分为"干净"(Clean)和"脏"(Dirty)两种.

  • 干净: 内存中未经修改的文件副本.
  • 脏: 内存中文件的修改副本.

未使用的页面是内存的空闲部分. 使用中的页面是指正在使用的 RAM.

干净页面可以删除, 因为它们总是可以使用数据重新创建. 但脏页不能删除, 否则修改过的数据就会丢失.

Kswapd 是 Linux 内核的一部分, 它能将已使用的内存转换为空闲内存. 当内存达到一定水平时, 它就会被触发. Kswapd 会开始回收内存. 当有足够的空间时, 回收过程就会停止. 内存回收的方式是删除页面或将其移至 zRam 进行压缩.

低内存杀手(Low Memory Killer)

当采取预防措施仍无法获得足够内存空间时, 系统会调用该功能. 系统使用 onTrimMemory 方法通知应用程序内存不足, 应减少分配. 如果内存不足, 低内存杀手就会被激活. 它将根据特定的评分系统开始停止进程.

低内存杀手(LMK)

LMK 会从表格顶部开始向下杀死进程. 设备制造商可以更改 LMK 的行为. 让我们在一个完整的场景中看看这些例子.

在低端设备上, 当用户开始逐渐使用多个应用程序时, 垃圾回收程序会根据应用程序使用的内存接近极限时开始运行. 当Android操作系统达到 Kswapd 临界值时, 页面会被移至 zRam 并压缩或清理, 以释放内存空间. 在 Kswapd 无法应付的情况下, 低内存杀手就会发挥作用, 开始杀死应用程序.

这种情况不可取, 对用户不利. 假设用户正在使用应用程序 X, 当内存使用率较高时, 他将其置于后台并切换到应用程序 Y. 这意味着用户在进行重要交易时会损失时间和数据.

内存管理

导致应用程序占用过多内存的原因有很多. 导致内存泄漏的情况, 库选择, 创建过多嵌套的即时对象或执行大型操作等. 可以在 Android Studio 中使用 Memory ProfilerPerfetto 等监控工具来检测和调试这些情况.

  • 内存泄漏: 妨碍垃圾回收检测和回收未使用的对象. 因此, 它会导致我们在小于 100% 的内存上运行.
  • 库选择: 应用程序中使用的库对应用程序有一定的负载, 从编译或运行时执行到它们使用的方法.
  • 应用大小: 我们在应用中添加的所有内容(使用 PNG 代替 SVG 等)都会影响应用程序的大小. 缩小 APK 大小可显著减少内存使用量.
  • 基于工厂的结构: 在代码复杂度高, 对象过多或操作密集的地方创建对象时, 可以考虑使用工厂结构.
  • 对象池: 如果应用程序中重复使用同一类型的对象, 则可以使用对象池. 当对象未被使用并将被销毁时, 如果将来需要再次使用, 可以从对象池中提取对象, 而不是在需要时通过防止对象销毁来重新创建对象. 这需要良好的同步和使用.

影响 Android 应用程序内存使用的外部因素很多. 即使是相同设备的不同 API 版本或不同的应用程序和权限, 也可能存在使用上的差异. 为此, 应做好系统监控和测试工作. 重要的是要关注应用程序的内存和生命周期, 并以尽量减少数据丢失的方式进行开发.