在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、前置思考

1.1 为什么系统重启后"打开过的应用更快"?

打开一个应用、再退出、再打开——第二次明显更快。为什么?因为第一次读的数据还在**页缓存(Page Cache)**里:

页缓存 = 内核用空闲内存"缓存"磁盘数据
  → 第一次读: 磁盘 → 内存 (慢,~10ms/次)
  → 第二次读: 内存命中 → 应用 (快,~0.1ms/次)
  → 快 100 倍!

1.2 冷热数据的本质

热数据: 频繁访问的数据 → 留在内存 → 命中率决定性能
温数据: 偶尔访问的数据 → 缓存与磁盘之间
冷数据: 长期不访问的数据 → 回写磁盘 → 释放内存

问题: 内存是有限的,怎么判定谁冷谁热?
答案: LRU 链表(最近最少使用)

1.3 内存压力来了怎么办?

内存不足 → 内核要"抢回"缓存内存
  → 按什么顺序回收?
  → 回收多少?
  → 脏页(未写回的数据)必须先写回!

这就是 内存压力分级回收 要解决的问题

二、核心原理

2.1 Page Cache 页缓存

Page Cache: 内核的磁盘缓存层
  → 读文件: 数据先进 Page Cache → 应用直接读内存
  → 写文件: 先写 Page Cache (脏页) → 后台异步写回磁盘

好处:
  ① 读加速: 缓存命中免磁盘 IO
  ② 写合并: 多次小写合并成一次大写
  ③ 预读: 顺序读时提前加载后续页

内核组织:
  address_space (每个文件一个)
    → radix tree / xarray (按文件偏移索引)
    → 页对象 (struct page, 4KB/页)

2.2 LRU 链表

LRU (Least Recently Used): 最近最少使用

两条链表:
  活跃链表 (active): 经常被访问的页
  非活跃链表 (inactive): 很少被访问的页

访问流程:
  读页 → 在 inactive → 命中 → 移到 active 头部
  页在 active 长时间未访问 → 降级到 inactive
  内存不足 → 从 inactive 尾部回收 (先回收最冷的)

回写时机:
  inactive 尾部是脏页 → 先写回磁盘 → 再回收

2.3 内存压力分级回收(watermark)

内存水位线 (watermark): 内存余量的分级警戒线

  ┌─────────────────────────────────┐
  │ 可用内存充足 (绿色区)             │  → 正常分配
  ├─────────────────────────────────┤
  │ WMARK_LOW (黄色区)              │  → 后台开始回收
  ├─────────────────────────────────┤
  │ WMARK_MIN (红色区)              │  → 同步强制回收
  ├─────────────────────────────────┤
  │ 耗尽 (黑色区)                   │  → OOM Killer
  └─────────────────────────────────┘

回收压力分级:
  P0 轻: kswapd 后台异步回收 inactive 页
  P1 中: kswapd 加速 + 直接回收调用方
  P2 重: 同步回收 + 压缩内存(zram) + 逐出进程
  P3 极重: OOM Killer 杀进程

三、源码/API 深度解析

3.1 应用层:缓存监控与清理

import { fileIo } from '@kit.CoreFileKit';
import { bundleManager } from '@kit.AbilityKit';

// 监控应用缓存大小(字节)
function getAppCacheSize(): number {
  const context = getContext(this) as common.UIAbilityContext;
  const filesDir = context.filesDir;      // 私有文件目录
  const cacheDir = context.cacheDir;      // 缓存目录
  // 递归统计目录大小
  let size = 0;
  const statDir = (dir: string): void => {
    const list = fileIo.listFileSync(dir);
    for (const f of list) {
      const full = dir + '/' + f;
      try {
        const s = fileIo.statSync(full);
        if (s.isDirectory()) {
          statDir(full);
        } else {
          size += s.size;
        }
      } catch (e) { /* 忽略损坏项 */ }
    }
  };
  statDir(cacheDir);
  statDir(filesDir);
  return size;
}

// 清理应用缓存(主动回写的应用层体现)
function cleanAppCache(): void {
  const context = getContext(this) as common.UIAbilityContext;
  const cacheDir = context.cacheDir;
  try {
    // 删除缓存目录内容(图片缓存、临时文件等)
    fileIo.rmdirSync(cacheDir);
  } catch (e) {
    console.error('清理缓存失败: ' + JSON.stringify(e));
  }
}

3.2 内核:LRU 回收流程(伪代码)

// 内存回收核心入口(内核)
// 当可用内存低于 watermark 时触发
unsigned long shrink_inactive_list(unsigned long nr_to_scan,
                                    struct lruvec *lruvec)
{
    // 1. 从 inactive 链表尾部批量摘取候选页
    struct page *page = lru_to_page(lruvec->lists[LRU_INACTIVE_FILE]);
    // 2. 逐页判断
    for (each page) {
        // 2.1 是脏页 → 先加入写回队列
        if (PageDirty(page) && !PageWriteback(page)) {
            // 写回: 回写磁盘 → 清脏标记
            // (主动回写: balance_dirty_pages 也在此触发)
            submit_bh_writeback(page);
            continue;   // 写回完成后才能回收
        }
        // 2.2 被引用 (正在使用) → 跳过
        if (page_referenced(page)) {
            move_to_active_list(page);   // 升回活跃链表
            continue;
        }
        // 2.3 无引用非脏页 → 直接释放
        __remove_page_from_lru(page);
        free_unref_page(page);           // 归还伙伴系统
    }
    // 3. 记录回收数量 → 驱动 kswapd 继续/停止
    return nr_reclaimed;
}

3.3 内存压力水位线判定

// watermark 判定(内核)
// 每次分配内存前检查
bool zone_watermark_ok(struct zone *zone, unsigned int order,
                       long mark, int alloc_flags)
{
    long available = zone_watermark_ok_zone(...);
    // WMARK_MIN: 最低警戒线
    if (available < min) {
        // 调用直接回收
        direct_reclaim();   // 同步回收 → 调用方等待
        // 仍不足 → OOM
        if (available < min) { out_of_memory(); }
    }
    // WMARK_LOW: 后台回收线
    else if (available < low) {
        wakeup_kswapd(zone);   // 唤醒后台回收线程
    }
    return true;
}

四、企业级实战落地

4.1 冷热分层存储架构

介质 特点 存放数据
L1 内存缓存 最快 · 易失 热数据(会话、排行榜)
L2 磁盘缓存目录 快 · 持久 温数据(图片、媒体)
L3 用户目录/云端 慢 · 可靠 冷数据(历史记录、备份)

4.2 完整示例:冷热分层管理演示

@Entry
@ComponentV2
struct PageCacheDemo {
  @Local logs: string[] = [];
  @Local state: string = '未开始';

  private runHotDemo(): void {
    this.logs = [];
    this.state = '热数据命中';
    this.log('🔥 热数据读取演示 (Page Cache 命中)');
    this.log('第 1 次读文件: 磁盘 → 页缓存 → 应用');
    this.log('  耗时: 8.2ms (磁盘 IO)');
    this.log('  页状态: inactive 链表 → 命中升 active');
    this.log('第 2 次读文件: 页缓存命中 → 应用');
    this.log('  耗时: 0.08ms (纯内存, 快100倍)');
    this.log('第 3 次读文件: 页缓存命中 → 应用');
    this.log('  耗时: 0.07ms ✅');
    this.log('结论: 热数据留在内存,命中率决定性能');
  }

  private runPressureDemo(): void {
    this.logs = [];
    this.state = '内存压力回收';
    this.log('🌊 内存压力分级回收演示');
    this.log('可用内存 80% → 绿色区: 正常分配');
    this.log('可用内存 45% → 黄色区(WMARK_LOW):');
    this.log('  唤醒 kswapd 后台回收 inactive 页');
    this.log('可用内存 25% → 红色区(WMARK_MIN):');
    this.log('  同步直接回收 → 脏页先写回磁盘');
    this.log('可用内存 5% → 黑色区: OOM Killer');
    this.log('  按 oom_score 逐出最低价值进程');
    this.log('结论: 分级回收避免系统崩溃');
  }

  build() {
    Column({ space: 12 }) {
      Text('🧊 冷热数据分层管理').fontSize(20).fontWeight(FontWeight.Bold)
      Text('状态: ' + this.state).fontSize(13).fontColor('#4FC3F7')

      Row({ space: 8 }) {
        Button('热数据命中').layoutWeight(1).height(40).fontSize(12)
          .onClick(() => this.runHotDemo())
        Button('压力回收').layoutWeight(1).height(40).fontSize(12)
          .onClick(() => this.runPressureDemo())
      }
      .width('100%')

      Scroll() {
        Column() {
          ForEach(this.logs, (l: string) => {
            Text(l).fontSize(11).lineHeight(18).fontColor('rgba(255,255,255,0.8)').width('100%')
          }, (l: string, i: number) => l + i)
        }.width('100%')
      }
      .layoutWeight(1).width('100%').scrollBar(BarState.Off)
    }
    .width('100%').height('100%').padding(16)
    .backgroundColor('#0D1B2A')
  }
}

4.3 冷热策略选择

热数据判定依据:
  ① 访问频率: 1分钟访问 > N 次 → 热
  ② 业务性质: 登录态/购物车 → 必须热
  ③ 预加载: 启动路径数据 → 主动预热

分层落地:
  内存层: LruCache / 内存数据库 (如 Redis 思想)
  磁盘层: 文件缓存 + 过期清理
  远端层: 云端按需拉取

五、问题排查与性能优化

问题 原因 解决
应用反复冷启动 页缓存被频繁回收 减少内存占用、调整 watermark
写卡顿明显 脏页堆积集中写回 调低 dirty_ratio、分散写
明明有内存却卡 内存碎片化 启用内存压缩/zram
缓存命中率低 LRU 判定不合理 业务层二次缓存
后台被杀 常驻内存过大 冷数据下沉到磁盘
内存泄漏缓慢增长 页面未释放 监测 shrink 效果

5.1 冷热数据优化清单

1. 按业务分缓存: 热数据内存缓存(短TTL),冷数据磁盘缓存(长TTL)
2. 主动预热: 应用启动时预加载首页/核心页数据到页缓存
3. 主动回写: 批量写操作集中 flush,减少脏页堆积
4. 监听内存: onMemoryLevel 回调 → 降级释放冷缓存
5. 监控指标: 缓存命中率、脏页比例、回收压力水位

六、高阶总结与最佳实践

  1. 缓存是性能之源:Page Cache 让第二次访问快 100 倍,善用缓存层是性能优化的第一课。
  2. LRU 管冷暖:活跃/非活跃双链表精细区分冷热,回收永远从最冷开始。
  3. 分级回收保稳定:watermark 三级警戒线让内存压力平滑释放,避免系统雪崩。
  4. 脏页先回写:任何回收前保证数据落盘,这是数据可靠性的底线。
  5. 应用层配合:业务层冷热分层 + 主动预热,与内核 Page Cache 形成双层加速。

一句话记住:冷热分层的本质是用"时间换空间、空间换时间"——热数据留在内存(LRU 管冷暖)、脏页及时回写(数据可靠)、内存压力分级回收(watermark 保稳定),应用层配合缓存分层与主动预热,才能让系统又快又稳。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐