内存与设备证据

如何定位重复端侧推理后的 Android 内存泄漏

重复推理时内存图上升是调查信号,不足以单独证明发生泄漏。

审核状态: 内容已校验;中文稿供用户审核 · 2026-10-03 · Fact IDs: MEMORY-03, COVE-LIFECYCLE-01

直接回答

重复推理后内存曲线升高是调查信号,不是泄漏结论。曲线可能包含正常缓存、延迟垃圾回收、保留的对话历史、原生张量、图像缓冲区或仍由活动任务拥有的对象。有效的泄漏测试要重复同一个有限工作负载,在相同生命周期检查点采样内存,由对应所有者关闭引擎和对话,给应用短暂稳定时间,再比较仍保留的实例,而不是只看一个峰值。Android Studio heap dump 可以检查托管对象;托管堆视图不能说明所有原生分配。 先定义一个可重复任务:同一个模型制品、提示结构、输出限制、图像尺寸或音频时长、后端请求和轮次数。记录模型加载前基线、加载后、任务峰值、输出释放后、对话关闭后和引擎关闭后。如果只有进程重启后内存才下降,这提示所有权或分配器保留问题,但还不能指出准确泄漏对象。若工具能提供指标,应分别记录 Java 堆、进程 RSS、原生堆、GPU 分配和文件缓存。 调查应检查对象数量是否随周期增长、Activity 或 Context 是否仍可达、观察者或协程是否持有 ViewModel、流式订阅是否在取消后存活、Camera ImageProxy 是否关闭,以及原生引擎是否恰好关闭一次。预热后和多次相同周期后各抓取堆快照,比较被保留对象类型和引用路径,再只改一个清理边界重测。一次分配后稳定可能是缓存;任务对象无限保留更像生命周期问题。没有设备、构建、工具、指标与原始运行序列,就不要发布内存增长数字。 测试生命周期,而不只是推理调用:旋转设备、离开再返回页面、流式期间取消、触发异常、后台再前台并重复任务。引擎创建和关闭应有明确所有者;初始化失败也必须进入清理路径。防止旧结果持有整个屏幕树。摄像头到模型链路中,即使推理抛异常,也要释放图像帧;音频路径则在收尾后释放缓冲与流。 报告应把观察与结论分开,例如“关闭后托管堆回到接近实测基线”“当前工具无法观测原生内存”或“Activity 通过该引用路径仍可达”。只有同一重复测试不再显示该对象路径,任务仍通过验收,才说泄漏已修复。这是工程验证指南,不是 Cove 基准测试结果。 流程:固定任务、制品、运行时、设备和周期;在基线、加载、峰值、取消后和关闭后采集匹配指标;检查协程、监听器、Activity/Context、媒体缓冲和原生资源所有者;每次只改一个清理动作并重复取消、异常、旋转和重进;最后明确证据不能解释的原生、GPU 或分配器行为。最终结果应是一份有界、可复现的诊断,而非断言所有本地 AI 都会泄漏。

每次重复翻译后内存都似乎增加

测试人员反复运行相同的短文本翻译,并在页面间导航,观察到内存持续上升。单张截图无法证明是模型缓存稳定增长、协程遗留、对话被保留,还是原生分配正常行为。

决策

用命名检查点重复同一工作负载,比较关闭后的对象引用,每次只改变一个生命周期所有者。

预期结果

团队得到可回归测试的解释,而不会把一个峰值误认为确定泄漏。

需要收集的证据

只有将以下证据与同一模型文件和测试运行关联起来,结论才可复核:

  • 重复会话的固定工作负载
  • 关闭后的对象引用路径
  • 堆与进程指标的分层记录

设备兼容结论须绑定具体设备、系统、模型、运行时、后端和工作负载,不能靠参数量推测。

实施流程

  1. 固定重复工作负载. 保持应用构建、模型文件、运行时、后端请求、提示、输出限制、媒体尺寸和周期数一致。将预热与测量周期分开,不混入其他屏幕或任务。
  2. 采集可比较的生命周期检查点. 记录基线、模型加载后、任务峰值、输出释放后、对话关闭后和引擎关闭后。区分堆与进程指标,并记录是否等待了垃圾回收或稳定时间。
  3. 追踪仍然可达的对象. 比较堆快照中关闭后仍可达的 Activity、Context、ViewModel、监听器、订阅者、图像、音频和会话对象。找出每个对象由谁持有,以及该引用是否应该跨屏幕存在。
  4. 修复一个清理路径后再做压力测试. 只改一个资源所有者或关闭路径,然后用相同周期测试取消、异常、旋转、导航和重进。确认任务结果仍通过验收,再说明怀疑的保留问题已解决。

让每个阶段的状态都可观察。失败信息应指出阶段、模型、运行时和恢复动作,同时避免记录用户私密内容。

验收标准

  • 证据记录模型、应用构建、运行时、设备、工作负载和准确内存指标。
  • 泄漏判断具有跨相同周期增长的保留对象或分配路径,并能关联到具体所有者。
  • 清理改动后重复任务仍成功,先前观察到的引用路径不再被保留。

决策对照表

维度选择或证据解读方式
一次性预热后上升可能是缓存或初始化成本需比较重复检查点;单个高值不是泄漏诊断。
关闭后仍保留托管对象检查堆引用路径堆快照有助于定位 Java/Kotlin 对象可达性和生命周期所有者。
原生或 GPU 内存不下降进程或运行时分配调查仅靠托管堆工具无法解释全部原生与驱动分配。

需要避免的失败模式

  • 把单次峰值当泄漏结论
  • 将所有内存指标混为一谈
  • 只验证推理成功而不验证关闭

还要避免静默回退、版本未固定、缺少取消处理,以及把不同配置的结果混在一起下结论。失败运行也应与成功记录一并保存。

最小复现记录

层级记录内容
设备厂商、型号、芯片、内存档位、操作系统版本
软件应用版本和 Git 提交号
模型系列、变体、版本、格式、文件长度、摘要、量化方式
运行时名称、版本、请求后端及实际后端证据
工作负载样例版本、输入摘要、提示词摘要、输出策略
结果完成、失败、取消、回退及保护隐私的诊断信息

发布检查表

  • ☐ 主要搜索问题已回答,且没有未经支持的数字。
  • ☐ 所有模型文件和运行时版本均已固定。
  • ☐ 代表性任务和失败处理策略均有说明。
  • ☐ 生命周期、取消、清理和回退均已测试。
  • ☐ 用户内容与网络边界均有说明。
  • ☐ 结论只适用于被记录的配置。
  • ☐ 新增测试结果时链接到原始方法或证据。

来源与相关证据

中文部署与排错专题见。奇连 AI 端侧专题。

常见问题

一次请求后内存不降就证明泄漏吗?

不能。缓存和分配器可能保留内存以便重用,垃圾回收也可能延迟。应重复固定任务,比较关闭后的保留对象,并将进程内存与托管堆区分。

为什么要在多次会话后抓取堆快照?

Activity、监听器、订阅者或对话对象可能只有在多个生命周期周期后才暴露问题。对一致检查点比较对象数量和引用路径,比单份快照更有信息量。

堆快照能确认原生引擎泄漏吗?

不能单独确认。堆转储检查托管对象;原生引擎、GPU 和分配器行为可能需要其他进程或运行时诊断。如果无法测量,应明确保留为未知。