Chromium 内存优化总结

一、概述

Chromium 在系统资源有限的嵌入式平台,常常遇到内存吃紧、性能恶化的问题,尤其体现在加载超大页面的时候。针对内存占用进行优化,是性能优化的关键。

二、Chromium 中的 Memory Reducing 方式

Chromium 对内存的处理主要集中在 RenderThreadImpl::OnMemoryPressure() 函数中;RenderThread 对象初始化的时候,会将 this 指针与该成员函数绑定,作为参数实例化一个 MemoryPressureListener 对象。

我们可以设计一个 MemoryMonitor 的类,不断地查询内存状态。当内存吃紧时,经由 RenderViewObserver 通知到 MemoryPressureListener 对象,便会回调 OnMemoryPressure() 这个函数,并传入内存压力等级:

1. 对于 DiscardableMemory 的清理

DiscardableMemory(以下简称 DM)是 Chromium 中用来缓存大对象(LOB)的内存管理类型,主要用于缺少内存交换空间(Swap)的移动终端或嵌入式设备、以及没有使用空闲内存来提升用户体验的桌面设备上。

DM 提供了一种较为简单的方式,能够更好地响应系统内存压力信号并及时释放内存。DM 有两种状态:锁定状态和解锁状态。处于锁定状态的 DM 不能被回收,解锁 DM 能够让操作系统按需回收。

DM 的实现中,定义了使用等级(usage level)去量化实例被使用的频率。随着某个实例被频繁地使用,其使用等级会相应增加。DM 以页为最小分配单位,由于内存对齐,所分配的总内存通常会比所申请的要大。因此对于小对象的内存分配,DM 并不是很有效率。需要注意的是,DM 的实例是线程不安全的,在使用时要确保没有资源竞争。


注意:由于 DM 的架构在 47 及以后的版本中有较大改动,以下的两种清理方式仅适用于 39 及以前的版本。

ReduceMemoryUsage 方法将每个 DM 实例的使用等级降低到基础等级及以下。当 DM 的平均使用等级较高时,过于频繁地调用这个方法会降低性能。但是如果调用的频率太低,又会导致内存膨胀,因此需要选择合适的频率来达到平衡。

ReduceMemoryUsageUntilWithinLimit 这个静态方法可以用来减少内存占用直到减少到给定的字节额度,让内存压力降低到正常水平。

Image Cache 的清理会直接影响到用户的使用体验。当滚动页面或切换 Tab 时会重新 decode 页面图片,造成视觉上的卡顿感。因此只在内存严重不足时,才会去清理 Image Cache,并且这个频率不能过高。

3. V8 中的 Garbage Collection

GC 是控制内存占用最后的手段,也是最关键的杀招,需要尽可能地去压榨出更多的内存。下面这个方法可以去通知 V8 去做一次 GC:

注意到 CollectAllAvailableGarbage 这个方法没有返回值,也没有指定释放的内存大小,它不会去做全面的 GC(全面的 GC 会潜在地导致 V8 的任务线程卡住)。关于 V8 的内存回收机制有很多文章分析得很透彻,在此不做详述。

三、MemoryPressureListener 的工作原理

MemoryPressureListener 提供了一个静态方法来响应平台上的内存压力信号。启动一个 Listener 只需要创建一个新的 MemoryPressureListener 实例,并将一个处理函数作为参数传入。

MemoryPressureListener 中定义了三个内存压力等级:None 等级表示内存足够使用,通常不会用来通知;Mederate 等级会尝试释放非急需且方便释放的内存;Critical 等级代表内存状态极度危险。

在 Linux 平台,如果没有足够的内存被归还给系统,导致系统可用内存不足,最终会触发 Kernel 里的 OOM (Out of Memory) killer。OOM killer 会杀掉最占用内存的进程,以腾出内存给系统使用,不致于让系统立刻崩溃。因此在内存状态为 Critical 的时候需要尽可能释放掉所有可能的内存。

通知 MemoryPresssureListener 内存状况:

值得注意的是,即便在同一个线程里,当收到系统内存吃紧的通知后,回调函数也不能保证会被同步执行。

四、Memory Monitor 的实现方式和策略

1. 关于 meminfo 的介绍

在 Linux 系统中,内核通过 /proc/meminfo 提供系统当前的内存详细信息:

随着时间的推移,系统的可用内存会越来越少,直观的体现为 MemFree 值不断减小,Cached 值会先增长后减少。对于启用了 Swap 的平台,系统会将内非活动内存换页到 Swap,以提高可用内存。

2. 对 RAM Cache 的清理

当系统内存不足时,有两种方式进行内存释放,一种是系统自己触发的内存回收,另一种是手工的方式。在某些嵌入式平台上,系统的内存回收不能及时有效地被触发,导致 cached 持续增长,MemFree 降到很低都没有释放,这时我们可以手工释放内存:

如果 page cache 中有脏数据时,操作 drop_caches 是不能释放的,必须通过 sync 命令将脏数据刷新到磁盘,才能通过操作 drop_caches 释放 page cache。

注:/proc 目录是 Linux 上的一个虚拟文件系统,我们可以通过对它的读写来与 kernel 实体间进行通信,从而对当前 kernel 的行为做出调整。

参考文档: