23、车载内存优化:内存泄漏检测(LeakCanary/MAT)、LMK与OOM策略定制、ZRAM与Swap优化、内存碎片整理

车载系统的内存,说实话,是个老大难问题。

我最早接触车载项目时,总觉得手机上的那套内存优化经验可以直接搬过来。结果呢?车机跑着跑着,导航卡死、音乐断流、倒车影像延迟……用户直接投诉到车厂。后来我才明白,车载内存优化的核心逻辑和手机完全不同——手机可以杀后台,车机你敢杀吗?仪表盘和中控屏的进程,一个都不能挂。

这一章,我们就来聊聊车载内存优化的四个关键方向:泄漏检测、LMK/OOM策略、ZRAM/Swap、碎片整理。说白了,就是怎么让车机在有限的物理内存下,跑得稳、跑得久。

核心观点:车载内存优化的目标不是“省内存”,而是“保证关键进程不挂”。一切策略都要围绕这个目标展开。

车载内存优化 内存泄漏检测 LMK / OOM 策略 ZRAM / Swap 内存碎片整理 LeakCanary MAT 分析 LMK 阈值调参 OOM 策略定制 ZRAM 配置 Swap 分区 BuddyInfo / 碎片指数 目标:保证关键进程不挂,提升系统稳定性 泄漏检测 → 策略定制 → 压缩交换 → 碎片整理

23.1 内存泄漏检测:LeakCanary 与 MAT 实战

内存泄漏,说白了就是对象该回收时没被回收。在车载系统里,泄漏的后果比手机严重得多——手机重启一下就行,车机你敢让用户重启吗?

LeakCanary 的使用

我个人习惯在开发阶段直接集成 LeakCanary 2.x。它不需要手动初始化,加个依赖就行:

// build.gradle
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'

启动后,LeakCanary 会自动监控 Activity、Fragment、ViewModel 等生命周期对象。一旦发现泄漏,它会生成一个 heap dump 并展示泄漏链。

我的经验:在车载项目中,我遇到过最典型的泄漏是「单例持有 Context」。比如一个全局的导航管理类,持有了 Activity 的 Context,导致 Activity 退出后无法回收。LeakCanary 直接定位到了那个单例的引用链,省了我大量排查时间。

MAT 深度分析

LeakCanary 适合日常开发,但遇到复杂泄漏(比如匿名内部类、Handler 消息队列),就需要 MAT 上场了。

MAT 的使用流程我简单说一下:

  1. adb shell am dumpheap <pid> /data/local/tmp/heap.hprof 导出堆快照
  2. 用 Android Studio 的 Profile 工具或 MAT 打开 hprof 文件
  3. 查看 Histogram,按对象数量排序,找异常多的类
  4. 右键查看 Merge Shortest Paths to GC Roots,排除软/弱引用

举个例子,我曾经发现 Message 对象数量异常多,追下去发现是一个 Handler 在后台线程不断发送延迟消息,而该 Handler 所在的 Activity 已经销毁了。这就是典型的「Handler 泄漏」。

注意:在车载系统上 dump heap 要小心。heap dump 本身会触发一次 GC,如果内存已经紧张,dump 过程可能导致系统卡死。我建议在系统空闲时(比如车辆熄火后)进行离线分析。

23.2 LMK 与 OOM 策略定制

LMK(Low Memory Killer)是 Android 内核层的内存回收机制。它根据进程的 oom_adj 值来决定杀谁。车载系统的问题在于:默认的 LMK 阈值对车机不友好。

LMK 阈值调参

默认的 LMK 阈值(比如 /sys/module/lowmemorykiller/parameters/minfree)是按手机场景设计的。手机可以杀后台应用,但车机上的导航、蓝牙电话、倒车影像都是前台进程,不能随便杀。

我建议的做法是:

  • 提高系统进程(system_server、surfaceflinger)的 oom_adj 值,让 LMK 最后才杀它们
  • 降低第三方应用的 oom_adj,让它们成为优先回收对象
  • 调整 minfree 阈值,给关键进程留出更多内存余量
// 示例:设置 system_server 的 oom_adj 为 -12(不可杀)
echo -12 > /proc/$(pidof system_server)/oom_adj

// 设置导航进程的 oom_adj 为 -8(极低优先级被杀)
echo -8 > /proc/$(pidof com.example.navi)/oom_adj

核心原则:车载系统的 LMK 策略应该是「保关键进程,杀非关键进程」。不要一刀切地使用手机默认配置。

OOM 策略定制

当 LMK 都来不及回收时,系统会触发 OOM(Out Of Memory)。默认的 OOM killer 行为是随机杀进程,这在车机上绝对不行。

我曾在项目中定制过 OOM 策略:

  • 在 kernel 层修改 oom_badness() 函数,给车载关键进程(如仪表盘、HMI)增加权重
  • 在用户空间注册一个 lowmemorykiller 监听器,当内存低于阈值时,主动释放缓存和后台服务
// 内核层修改示例(简化)
unsigned long oom_badness(struct task_struct *p, ...) {
    if (is_car_critical_process(p)) {
        return 0;  // 永远不被 OOM killer 选中
    }
    return default_oom_badness(p);
}

警告:修改内核 OOM 策略需要谨慎。我曾经因为把某个系统服务设为「不可杀」,导致内存耗尽后系统完全死锁。建议保留一个「最后防线」——比如让 watchdog 在内存极低时主动重启非关键服务。

23.3 ZRAM 与 Swap 优化

ZRAM 是用压缩内存来模拟交换空间。说白了,就是把不常用的内存页压缩后存起来,腾出物理内存给活跃进程用。车载系统内存小,ZRAM 几乎是必选项。

ZRAM 配置

ZRAM 的大小和压缩算法需要根据硬件调整。我一般这样配置:

// 设置 ZRAM 大小为物理内存的 25%
echo 512M > /sys/block/zram0/disksize

// 选择 lz4 压缩算法(比 lzo 更快)
echo lz4 > /sys/block/zram0/comp_algorithm

// 启用 ZRAM
mkswap /dev/block/zram0
swapon /dev/block/zram0

为什么选 lz4?因为车载系统对延迟敏感。lz4 压缩率略低,但解压速度比 lzo 快 30% 左右。你想想看,如果用户正在操作导航,突然因为换页导致卡顿,体验会很差。

我的经验:ZRAM 不是越大越好。我见过有人把 ZRAM 设为物理内存的 50%,结果压缩和解压占用了大量 CPU,导致系统整体响应变慢。建议控制在 20%-30% 之间。

Swap 分区

如果车机有 eMMC 或 UFS 存储,可以考虑使用 Swap 分区作为二级交换。但要注意:闪存的写入寿命有限,频繁 swap 会加速存储老化。

我建议的做法是:

  • 优先使用 ZRAM(内存压缩),不涉及闪存写入
  • 只在内存极度紧张时(比如同时开启导航+音乐+语音助手)才启用 Swap
  • 设置 swappiness 值(比如 10),让系统尽量少用 Swap
// 设置 swappiness 为 10(默认 60)
echo 10 > /proc/sys/vm/swappiness

23.4 内存碎片整理

内存碎片,说白了就是物理内存被分割成很多小块,导致无法分配大块连续内存。车载系统长时间运行后,碎片问题尤其严重。

碎片检测

通过 /proc/buddyinfo 可以查看内存碎片情况。我一般这样分析:

cat /proc/buddyinfo

// 输出示例:
// Node 0, zone   Normal   123   45   12   3   1   0   0   0   0   0   0

数字从左到右代表 2^0 到 2^10 页的可用块数。如果高阶块(比如 2^4 以上)数量很少,说明碎片严重。

碎片整理策略

Android 内核提供了 compact_memory 接口,可以手动触发内存压缩:

echo 1 > /proc/sys/vm/compact_memory

但我不建议频繁调用。因为 compact 过程会锁住内存分配器,导致系统短暂卡顿。我一般在以下时机触发:

  • 车辆熄火后(系统空闲)
  • 应用安装/卸载完成后
  • 系统升级后首次启动

避坑指南:我曾经在某个项目中,每 5 分钟执行一次 compact_memory,结果导致系统频繁卡顿。后来改为「碎片指数超过阈值时触发」,才解决问题。碎片指数可以通过 /proc/pagetypeinfo 计算。

长期运行建议

对于车载系统这种 7x24 小时运行的场景,我建议:

  • 使用 vmalloc 代替 kmalloc 分配大块内存(vmalloc 不要求物理连续)
  • 在应用层避免频繁创建和销毁大对象
  • 定期(比如每天一次)在系统空闲时执行 compact

嗯,这一章的内容就到这里。内存优化是个系统工程,泄漏检测、LMK 策略、ZRAM 配置、碎片整理,每个环节都不能忽视。我在实际项目中,往往是从泄漏检测入手,先解决「内存去哪了」的问题,再逐步调整策略。希望这些经验对你有帮助。

一句话总结:车载内存优化的核心不是「省」,而是「保」——保住关键进程,保住用户体验。

公众号:蓝海资料掘金营,微信deep3321