ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

使用 Claude Code 排查 HarmonyOS Snapshot 内存泄漏:ArkTS 持久化内容实践(TaoToken)

使用 Claude Code 排查 HarmonyOS Snapshot 内存泄漏:ArkTS 持久化内容实践(TaoToken) 1. 分时页切股后内存不降一个真实场景HarmonyOS ArkTS 应用里Snapshot 持久化内容是个容易被忽视的内存泄漏源头。我最近处理的一个分时页场景就很典型用户左右滑动切换股票页面用 Snapshot 把分时、日线、周线、五日线的数据缓存下来方便快速回切。功能上线后监控发现单次打开分时页进程内存申请峰值约 220 MB离开页面后仍有约 110 MB 留存万人 OOM 率一度偏高。问题不在 Snapshot 本身而在于持久化内容的生命周期没管住。ArkTS 里把闭包、组件引用、事件订阅对象塞进 Snapshot 或长生命周期 owner 后如果这些对象还挂在 GC Root 可达路径上切股产生的旧副本就不会被回收多次切股后持续累积。这篇就聚焦这个场景讲清楚怎么用 Claude Code 辅助分析泄漏路径交付可复制的配置片段、ArkTS 侧 Snapshot 使用骨架以及本地复现和验证内存增长的完整步骤。适合正在做 HarmonyOS 内存优化、想引入 AI 辅助排查的 ArkTS 开发者。2. 前置准备TaoToken 接入与 Claude Code 配置Claude Code 要能稳定分析 ArkTS 堆快照得先解决模型接入问题。我这边用 TaoToken 做统一入口它兼容 Anthropic 接口协议Claude Code 直接改 base_url 就能用不用改客户端逻辑。先拿 API Key打开 https://taotoken.net/api-keys 登录后创建一个 Key复制保存。注意 Key 只在创建时完整显示一次。然后配置 Claude Code 的环境变量。在项目根目录或 shell 配置里设置export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEYsk-你的key如果你用的是 Claude Code 的配置文件方式可以在~/.claude/settings.json里写{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key } }配置完执行claude进入交互输入/status确认模型连接正常。如果要做长期编码和 Agent 任务建议单独开 Coding Plan额度更稳适合跑多轮分析脚本生成https://taotoken.net/coding-plan 。注意base_url 只填到/api不要带多余路径否则 Claude Code 会拼接出错误的请求地址。3. 可复制配置ArkTS 侧 Snapshot 使用骨架泄漏的根因往往在业务代码里。下面是一个容易出问题的 Snapshot 持久化骨架我把它简化成最小可复现版本你可以直接对照自己的代码。// SnapshotStore.ets class SnapshotStore { private static instance: SnapshotStore; private cache: Mapstring, object new Map(); private listeners: Array() void []; static getInstance(): SnapshotStore { if (!SnapshotStore.instance) { SnapshotStore.instance new SnapshotStore(); } return SnapshotStore.instance; } // 问题点把闭包存回自身形成 owner 与 closure 的闭合环 register(key: string, builder: () object): void { const snapshot builder(); this.cache.set(key, snapshot); this.listeners.push(() { // 闭包捕获了 this 和 key this.cache.set(key, builder()); }); } release(key: string): void { this.cache.delete(key); // 漏了listeners 没有清理闭包仍持有 this } }这段代码的问题在于register把闭包 push 进listeners闭包又捕获了this而SnapshotStore是单例挂在模块作用域下。切股时每次register都新增一个闭包release只删了 cache 没删 listeners于是旧闭包连同它捕获的组件树一直可达。修复方向是让 owner 和 closure 的引用可断开release(key: string): void { this.cache.delete(key); this.listeners this.listeners.filter(l !l.toString().includes(key)); }更稳妥的做法是用 WeakMap 或显式持有 listener 句柄切股时精确移除。ArkTS 里组件生命周期aboutToDisappear是清理订阅的正确时机别把清理逻辑放在onPageHide里指望它一定触发。4. 验证请求本地复现与内存增长确认配置和代码骨架就位后要能本地复现内存增长否则分析没有依据。步骤分三步。第一步用 DevEco Profiler 采集.insight样本。按快照时序采五个点S1 打开页面前、S2 首次进入分时页、S3 多次切股后、S4 离开页面后、S5 等待一段时间后。导出后.insight可以按 ZIP 解压本次样本结构近似my_session/ ├── HEAP-1770961547611-14617.heapsnapshot ├── HEAP-1770961558481-14617.heapsnapshot ├── xxx_memory.db ├── xxx_native_hook.db └── manifest.json第二步让 Claude Code 跑分析流水线。把样本路径和时序说明一起给它path/to/分时页.insight 是鸿蒙 snapshot 数据。 快照时序S1 打开页面前S2 首次进入分时页S3 多次切股后S4 离开页面后S5 等待一段时间后。 请按内存泄漏分析流程运行脚本并生成报告到指定目录。Claude Code 会调用一键入口脚本产物按固定目录组织script/out/{insight_name}/ ├── overview.txt # 多快照趋势 ├── pss.txt # PSS 分区趋势 ├── S1/ S2/ S3/ # 单快照分析 └── S1_vs_S3/ # 双快照对比第三步看关键输出。overview.txt确认整体增长阶段pss.txt判断增长来自 ArkTS heap 还是 native heapcompare.txt找出基线快照与峰值快照之间增长最多的对象类型。本次样本里retainer.json的 cycles 字段识别出 4 个与DrawLineStorageOwner相关的环对应多次切股留下的独立副本。成功结果长这样S3 相比 S1DrawLineStorageOwner实例数从 1 涨到 4且每个实例都有一条-subroot-边把 closure 挂到 GC Root 下另有一个LexicalEnv从模块作用域指向 owner。只要任一外部锚点存在环和下游组件树就无法回收。5. 本篇常见错排查排查过程中踩过的坑集中在几处列出来对照。报错一heapsnapshot解析出来节点数为 0。多半是没按snapshot.meta里的node_fields和edge_fields做数组压缩解码。.heapsnapshot是 JSON 对象图nodes 和 edges 是扁平数组得按字段宽度切分。让 Claude Code 先读 meta 再写解析脚本别硬编码字段顺序。报错二retainer path 只给出一条最短路径看不到环。最短路径只反映线性引用环结构要看retainer.json里的 cycles 字段。如果脚本没输出 cycles检查环检测是否在双快照对比之后运行顺序错了会漏掉新增的环。报错三PSS 涨了但 ArkTS heap 没涨。说明增长在 native 侧看xxx_native_hook.db。别把 native 增长当成 ArkTS 泄漏去改业务代码方向会跑偏。报错四修复后内存仍不降。检查aboutToDisappear是否真的执行以及事件总线、全局注册表里是否还有残留引用。循环引用本身不致命致命的是它仍可从 GC Root 到达。用dominator_tree看保留量确认修复后可疑对象的 retained size 是否下降。报错五Claude Code 分析结论和实际业务对不上。模型擅长整合资料和生成脚本但业务生命周期语义必须人工确认。把源码路径喂给它让它读aboutToDisappear和订阅注册逻辑再下结论。6. 把分析沉淀成可复用资产这套流程跑通后真正有价值的不是某次结论而是把知识、脚本和判断流程固化下来。我试过把每轮分析产出落到文件系统按用途拆成五类领域知识Markdown/Skill、分析方法论Skill、分析工具Python 脚本、输出规范报告 Skill、执行入口Prompt 模板。这样换一个 snapshot 样本或换一个开发者流程仍可复现。工具链分层组织顶层一键入口底下按基础层、单快照分析、辅助工具拆开analyze_insight.py # 一键入口 ├── lib/ # 共享基础层 │ ├── heap_core.py # 数据加载与图构建 │ └── leak_config.py # 业务配置加载 ├── analysis/ # 单快照与双快照分析 │ ├── retainer_path.py # 引用链追溯 │ ├── cycles.py # 环检测 │ ├── compare.py # 双快照对比 │ └── visualize.py # 交互式 HTML 可视化 └── tools/ # 辅助工具 ├── snapshot_timeline.py └── inspect_node.py一条命令跑完整条流水线python3 script/analyze_insight.py path/to/xxx.insight需要交叉验证时把相同背景资料和样本给另一个模型独立跑一遍两边一致的结论可信度更高分歧点交人工裁决。脚本负责可重复计算模型负责编排和解释开发者负责判断业务生命周期和修复策略。如果你也在做 HarmonyOS 内存优化建议先把 Claude Code 的接入配好再按上面的骨架搭一套自己的分析流水线。模型对话入口在 https://taotoken.net 接入文档在 https://taotoken.net/doc API Key 管理在 https://taotoken.net/api-keys 。长期跑编码和 Agent 任务的话Coding Plan 更合适https://taotoken.net/coding-plan 。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进