RNOH 资源泄漏或OOM定位
1 问题概述
1.1 什么是资源泄漏和OOM
资源泄漏是指应用在分配资源(内存、文件句柄、网络连接等)后,未能在适当时机释放,导致资源持续增长。当内存持续增长超过系统限制时,应用会被系统强制终止,即OOM(Out of Memory)。
1.2 RNOH应用资源泄漏的特点
RNOH应用涉及多层内存管理,资源泄漏可能发生在任何一层,且跨层引用关系复杂,定位难度较高:
- JS引擎堆(Hermes):JS对象、闭包、Promise、全局变量
- Native C++堆:TurboModule实例、ShadowNode、ComponentInstance、动画节点、napi_ref持有的引用
- ArkTS运行时:ArkUI组件实例、TurboModule ArkTS实现、被napi_ref阻止GC的对象
- 系统资源:文件句柄、网络连接、图片解码缓冲区、VSync句柄、定时器
1.3 跨层引用关系
RNOH中关键的跨层引用关系是泄漏分析的核心:
JS对象 ←── jsi::Value/HostObject ──── C++对象
↑ │
│ │
└──── napi_ref ←── NapiRef ───────────┘
│
└── ArkTS对象(被阻止GC)
- **JS → C++**:通过
jsi::HostObject持有C++对象(如BlobCollector) - C++ → JS:通过
jsi::Value、CallbackWrapper持有JS对象 - C++ → ArkTS:通过
napi_ref(封装为NapiRef)持有ArkTS对象 - **ArkTS → C++**:通过TurboModule实例引用C++ Context
1.4 常见泄漏现象
| 现象 | 说明 |
|---|---|
| 反复进入退出页面后内存持续上涨 | 页面相关对象未被释放 |
| 调用Native接口后内存无法回落 | Native侧持有JS对象或资源未释放 |
| 页面已销毁但JS/Native对象仍长期存活 | 跨层引用阻止GC |
| 长时间运行后明显内存膨胀 | 缓存无上限、监听器累积、资源未清理 |
| 最终触发OOM被系统终止 | 内存超过系统限制 |
2 排查思路和整体分析
2.1 整体排查思路
资源泄漏定位的核心是判断泄漏发生在哪一层,然后使用对应工具深入分析:
1. 复现问题 → 使用稳定可重复的方式复现场景
2. 观察整体内存趋势 → 使用hidumper采集多次操作后的内存数据
3. 初步判断泄漏类型 → 根据AnonPage other和Native Heap的增长情况判断
4. 采集JS内存快照 → 如果怀疑JS层问题,使用React Native DevTools
5. 采集Native分配信息 → 如果怀疑Native层问题,使用DevEco Profiler Allocation
6. 结合代码审查 → 定位具体泄漏代码
7. 修复并验证 → 确认内存不再持续增长
2.2 泄漏分类
RNOH应用的资源泄漏可分为四大类:
类别1:JS对象被JS自身持有
完全发生在JS层,与RNOH框架无直接关系,但在RN应用中常见。
| 原因 | 说明 | 快照特征 |
|---|---|---|
| 事件监听器未移除 | addEventListener后组件卸载时未removeEventListener | 引用链经过EventEmitter |
| 定时器未清理 | setInterval/setTimeout未在组件卸载时清除 | 引用链经过Timer对象 |
| 全局缓存无上限 | Map/Set/Array持续增长无淘汰策略 | 引用链顶部为global |
| 闭包持有大对象 | 回调函数闭包捕获了页面对象或大数据 | 引用链经过Closure |
| 未完成Promise | Promise长期pending,持有resolve/reject闭包 | 引用链经过Promise对象 |
快照标签:global、Closure、GC roots
类别2:JS对象被Native/C++持有
发生在JS与Native的跨语言交互层,是RNOH应用中最常见且最难定位的泄漏类型。
原因1:napi_ref未释放
napi_ref是C++侧持有JS对象的强引用。如果创建后未调用napi_delete_reference,对应的JS对象永远不会被GC。
RNOH框架通过NapiRef RAII包装器管理napi_ref生命周期,但以下场景仍存在泄漏风险:
RNArkTSComponentDelegate使用原始napi_ref而非NapiRefRAII包装,异常路径可能泄漏- TurboModule析构时通过
runTask将NapiRef投递到目标线程销毁,如果TaskExecutor已失效,任务可能永远不执行 TurboModuleFactory析构时同样依赖TaskExecutor投递清理任务
原因2:LongLivedObjectCollection中的CallbackWrapper未释放
LongLivedObjectCollection用于阻止JSI对象(如RNOHPromise、CallbackWrapper)被GC。对象通过allowRelease()释放。
RNOH通过CallbackGuard/CallbackGuardRegistry机制管理释放:
CallbackGuard在析构时调用release()→CallbackWrapper::allowRelease()CallbackGuardRegistry在达到128条阈值时自动压缩已释放的GuardCallbackGlobalRegistry静态单例跟踪所有模块的Registry
泄漏风险:
- 如果Promise永远不resolve/reject,对应的
CallbackWrapper永远不会被释放 - 如果
jsInvoker->invokeAsync投递的releaseAll()任务未执行(JS队列已关闭),CallbackWrapper泄漏 CallbackGlobalRegistry使用weak_ptr避免阻止析构,但如果unregisterModule未被调用,过期条目可能残留
原因3:HostObject依赖JS GC释放Native资源
HostObjectProxy通过new分配,依赖JSVM finalizer回调delete。如果JS对象未被GC(如被全局变量持有),HostObjectProxy及其持有的shared_ptr<HostObject>永远不会释放。
典型案例:BlobCollector(HostObject)的析构触发BlobTurboModule::release()释放blob数据。如果JS Blob对象未被GC,blob数据持续堆积。
快照标签:HostObject、Global handles
类别3:纯Native/C++泄漏
完全发生在C++层,不涉及JS对象。
原因1:TurboModule缓存无淘汰
TurboModuleProvider::m_cache是一个unordered_map<string, shared_ptr<TurboModule>>,模块一旦创建就永久缓存,没有淘汰或清除机制。每个缓存条目持有:
NapiRef(阻止ArkTS模块对象GC)CallbackGuardRegistry(可能持有pending回调)Context中的shared_ptr图(jsInvoker、taskExecutor、eventDispatcher、scheduler等)
原因2:图片URI缓存无上限
ImageSourceResolver::remoteImageSourceMap是一个unordered_map<string, string>,每个远程图片URI解析后永久缓存,没有大小限制或淘汰策略。对于图片密集型应用(如信息流),该Map持续增长。
原因3:VSync/UITicker回调中的原始指针泄漏
VSyncListener::scheduleNextVsync和UITicker::requestNextTick使用new分配weak_ptr传递给C回调。如果VSync句柄在回调触发前被销毁(如应用退出),weak_ptr永远不会被delete。
原因4:Subject/Observer原始指针
Subject::m_observers使用原始指针存储观察者。如果观察者析构时未正确调用unregisterObserver(如对象切片、异常路径),悬空指针残留。
原因5:静态单例跨实例累积
多个静态单例在RNInstance销毁后仍保留数据:
| 单例 | 数据 | 清理机制 | 泄漏风险 |
|---|---|---|---|
TurboModuleProvider::m_cache | 模块缓存 | 随Instance析构 | 无(Instance级) |
CallbackGlobalRegistry | 模块回调注册 | unregisterModule | 未调用时残留 |
RNOHMarker::listeners | 性能标记监听器 | notifyListeners时压缩 | 不notify时累积 |
ImageCacheMemoryCache | 图片内存缓存 | 内存压力回调清空 | 全局单例不析构 |
PerformanceMetricsRegistry | 性能指标 | removeRNInstanceMetrics | 未调用时残留 |
FreezeStackCollector | 冻屏栈缓存 | unregisterRuntime | 未调用时残留 |
类别4:系统资源泄漏
| 资源类型 | 泄漏场景 | 影响 |
|---|---|---|
| 文件句柄 | 打开后未关闭,异常路径跳过close | 句柄数耗尽,后续open失败 |
| 网络连接 | HTTP请求未设置超时,回调未清理 | 连接数耗尽,内存增长 |
| 图片解码缓冲区 | 大图片解码后未及时释放 | Native Heap快速增长 |
| VSync句柄 | 注册后未注销 | 持续触发回调,CPU和内存开销 |
| 定时器 | libuv Timer未取消 | 事件循环无法退出,内存残留 |
2.3 初步判断泄漏类型
| 内存表现 | 初步判断 | 优先分析工具 |
|---|---|---|
| AnonPage other持续增长,Native Heap基本稳定 | JS对象被JS持有 | React Native DevTools |
| AnonPage other和Native Heap都增长,前者更明显 | JS对象被C++持有 | React Native DevTools + Allocation |
| 仅Native Heap增长,匿名页基本稳定 | 纯Native对象泄漏 | DevEco Studio Profiler / Allocation |
3 分步排查过程(工具使用)
3.1 步骤1:复现问题
使用稳定可重复的方式复现场景:
- 反复进入和退出某个页面(建议10次以上)
- 多次调用某个Native API
- 连续执行上传、下载、解析、渲染等操作
- 最后返回首页或离开目标页面,模拟组件卸载和页面销毁
3.2 步骤2:观察整体内存趋势
hdc shell hidumper --mem $(pidof com.example.rnoh)
每次执行泄漏操作后采集一次,重点观察:
| 指标 | 含义 | 泄漏指向 |
|---|---|---|
| AnonPage other | JS引擎堆 + 应用私有匿名页 | JS对象堆积 |
| Native Heap | C++/ArkTS直接分配的内存 | Native对象泄漏 |
| Pss | 进程实际占用物理内存 | 总体内存趋势 |
注意:最后一次操作后,先在React Native DevTools中主动触发GC,或等待约2分钟再采集,避免因GC延迟误判。
3.3 步骤3:初步判断泄漏类型
根据AnonPage other和Native Heap的增长情况,判断泄漏发生在哪一层:
| 内存表现 | 初步判断 | 优先分析工具 |
|---|---|---|
| AnonPage other持续增长,Native Heap基本稳定 | JS对象被JS持有 | React Native DevTools |
| AnonPage other和Native Heap都增长,前者更明显 | JS对象被C++持有 | React Native DevTools + Allocation |
| 仅Native Heap增长,匿名页基本稳定 | 纯Native对象泄漏 | DevEco Studio Profiler / Allocation |
3.4 步骤4:采集JS内存快照
使用React Native DevTools进行分析:
# 启动react-devtools
npx react-devtools
# 打通端口
hdc rport tcp:8097 tcp:8097
操作步骤:
- 通过Metro加载bundle
- 打开React Native Dev Menu → Open React Native DevTools
- 进入Memory页签
- 抓取第一次快照(首页baseline)
- 执行多次泄漏操作并触发GC
- 回到首页抓取第二次快照
- 选择第二次快照 → 顶部选择"比较" → 以baseline为基准 → 按大小增量排序
JS快照分析技巧:
| 快照特征 | 可能原因 | 排查方向 |
|---|---|---|
| 大量相同类型对象增量 | 集合无上限增长 | 检查Map/Set/Array的清理逻辑 |
| 对象被Closure持有 | 闭包捕获 | 检查回调函数捕获的变量 |
| 对象被HostObject持有 | C++侧持有JS对象 | 检查napi_ref、jsi::Value生命周期 |
| 对象被Global handles持有 | JSI全局引用 | 检查LongLivedObjectCollection |
| Detached DOM/组件 | 已卸载但仍被引用 | 检查事件监听器、定时器 |
3.5 步骤5:采集Native分配信息
如果怀疑Native/C++持有问题,使用DevEco Studio Profiler > Allocation:
- 创建Session并开始录制
- 执行多次泄漏操作
- 等待1到2分钟后停止录制
- 切换到Native Heap
- 在Details区域中过滤
Created & Existing - 在Call Trees中查看未释放对象的调用路径
如果构建产物剥离了符号表,需要手动导入符号文件(unstripped.so)。
Native Allocation分析技巧:
- 过滤
Created & Existing:只显示创建后仍未释放的对象 - 按调用栈聚合:找出哪个分配路径产生了最多未释放对象
- 对比两次录制:确认增长趋势是否持续
- 符号化:导入unstripped.so获取完整函数名和行号
3.6 步骤6:监控内存压力事件
RNOH框架通过onMemoryLevel回调响应系统内存压力:
// RNOHAppNapiBridge.cpp
void onMemoryLevel(int level) {
// 通知所有活跃RNInstance
for (auto& instance : instances) {
instance->onMemoryLevel(level);
}
}
ImageCacheMemoryCache注册了内存压力回调:
MEMORY_LEVEL_CRITICAL:清空全部缓存MEMORY_LEVEL_MODERATE/MEMORY_LEVEL_LOW:清空一半缓存
如果应用收到频繁的内存压力事件,说明内存已接近系统限制,需要重点排查泄漏。
3.7 步骤7:OOM现场紧急分析
当应用因OOM被系统终止时:
- 查看系统日志中的
ProcessDied或MEM_RECLAIM事件 - 确认进程终止前的内存占用(Pss)
- 回查hilog中的内存压力事件(
onMemoryLevel) - 如果可能,在内存压力事件回调中添加日志,记录各层内存状态
- 使用hidumper定期采样,绘制内存增长曲线,判断增长速率和拐点
4 根因定位
4.1 案例1:事件监听器未移除导致JS泄漏
现象:反复进入退出某页面后,AnonPage other持续上涨,Native Heap基本稳定。
分析步骤:
- 在React Native DevTools中抓取两次快照(baseline + 操作后)
- 快照对比,按大小增量排序,发现页面对象的 retained size 持续增大
- 查看保留器,引用链为:
GC roots → EventEmitter → listener → PageComponent - 确认页面卸载时未调用
removeEventListener
根因:页面组件在useEffect中注册了事件监听器,但组件卸载时未在清理函数中移除监听器,导致EventEmitter持续持有页面对象引用,阻止GC。
4.2 案例2:TurboModule回调未释放导致OOM
现象:长时间运行后Native Heap和AnonPage other同步增长,最终OOM。
分析步骤:
- hidumper显示AnonPage other和Native Heap均持续增长
- JS快照发现大量
RNOHPromise对象被HostObject/Global handles持有 - Allocation显示
CallbackWrapper对象持续创建但未释放 - 排查代码发现某TurboModule的异步方法在特定条件下未调用resolve/reject
根因:TurboModule的异步方法在某些异常路径下未调用resolve或reject,导致Promise长期pending,对应的CallbackWrapper被LongLivedObjectCollection持有,永远不会释放。
4.3 案例3:图片URI缓存无限增长
现象:信息流应用中,滑动浏览大量图片后内存持续上涨。
分析步骤:
- hidumper显示Native Heap持续增长
- Allocation显示
ImageSourceResolver相关分配持续增加 - 代码审查发现
remoteImageSourceMap无大小限制 - 确认应用展示了数百张不同的远程图片
根因:ImageSourceResolver::remoteImageSourceMap是一个unordered_map<string, string>,每个远程图片URI解析后永久缓存,没有大小限制或淘汰策略。对于图片密集型应用,该Map持续增长导致内存泄漏。
4.4 案例4:Blob数据依赖GC释放导致OOM
现象:频繁使用文件上传/图片选取功能后OOM。
分析步骤:
- Native Heap快速增长,AnonPage other增长较慢
- Allocation显示大量blob数据缓冲区未释放
- 代码审查发现
BlobCollector(HostObject)的析构触发blob释放 - 但JS Blob对象被业务代码全局缓存,GC无法回收
根因:BlobCollector(HostObject)的析构触发BlobTurboModule::release()释放blob数据,但JS Blob对象被业务代码全局缓存,GC无法回收JS对象,导致Native侧的blob数据永远不会释放。
4.5 案例5:多实例场景下静态单例累积
现象:反复创建和销毁RNInstance后内存持续增长。
分析步骤:
- 确认应用使用了多RNInstance模式
- hidumper显示每次创建新Instance后内存上涨,销毁后不完全回落
- 代码审查发现多个静态单例(
CallbackGlobalRegistry、RNOHMarker::listeners等)在Instance销毁后仍保留数据 - 确认
unregisterModule、removeListener等清理方法是否被正确调用
根因:多个静态单例在RNInstance销毁后仍保留数据,如果unregisterModule、removeListener等清理方法未被正确调用,过期条目会持续累积,导致内存泄漏。
5 解决方案
5.1 JS事件监听器泄漏修复
问题:页面组件在useEffect中注册了事件监听器,但组件卸载时未移除。
解决方案:
useEffect(() => {
const listener = eventEmitter.addListener("event", handler);
return () => listener.remove(); // 组件卸载时移除监听
}, []);
最佳实践:
- 所有
addEventListener/addListener必须在组件卸载时移除 - 使用
useEffect返回清理函数 - 对于类组件,在
componentWillUnmount中移除监听器
5.2 TurboModule回调未释放修复
问题:TurboModule的异步方法在某些路径下未调用resolve/reject。
解决方案:
确保所有Promise路径都有resolve或reject:
async myMethod(): Promise<string> {
try {
const result = await doSomething();
return result; // resolve
} catch (e) {
throw e; // reject
}
// 确保没有遗漏的路径
}
最佳实践:
- 所有异步方法必须确保Promise在所有路径都有resolve或reject
- 使用
try-catch包裹所有可能抛出异常的代码 - 对于复杂的异步流程,使用
Promise.race设置超时
5.3 图片URI缓存无限增长修复
问题:remoteImageSourceMap无大小限制,持续增长。
解决方案:
- 增加LRU淘汰策略:
void updateImageSourceMap(const std::string& uri, const std::string& resolvedUri) {
std::lock_guard lock(m_imageSourceMapMutex);
remoteImageSourceMap[uri] = resolvedUri;
// 超过上限时淘汰最早的条目
if (remoteImageSourceMap.size() > MAX_IMAGE_URI_CACHE) {
remoteImageSourceMap.erase(remoteImageSourceMap.begin());
}
}
- 限制缓存大小上限:
static constexpr size_t MAX_IMAGE_URI_CACHE = 1000;
- 页面退出时清理不再需要的图片URI映射。
5.4 Blob数据依赖GC释放修复
问题:Blob数据依赖JS GC释放,但JS对象被全局缓存。
解决方案:
- 业务代码避免全局缓存Blob对象:
// 修改前:全局缓存Blob
const blobCache = new Map();
blobCache.set(id, blob);
// 修改后:使用完毕后主动释放
const blob = await fetchBlob(id);
useBlob(blob);
blob.close(); // 主动释放
- 在Native侧增加blob数据的主动释放接口:
// TurboModule接口
releaseBlob(blobId: string): void;
// 业务代码调用
BlobModule.releaseBlob(blobId);
- 不完全依赖GC,对于关键资源提供主动释放接口。
5.5 多实例场景下静态单例累积修复
问题:静态单例在RNInstance销毁后仍保留数据。
解决方案:
- 确保RNInstance销毁时所有清理回调被正确执行:
~RNInstanceInternal() {
// 确保所有清理方法被调用
CallbackGlobalRegistry::instance().unregisterModule(name_, m_callbackGuardRegistry.get());
RNOHMarker::removeListener(m_markerListener);
PerformanceMetricsRegistry::getInstance().removeRNInstanceMetrics(m_id);
FreezeStackCollector::getInstance().unregisterRuntime(m_id);
}
对静态单例增加Instance级别的清理逻辑。
使用
weak_ptr存储Instance相关数据,允许自动回收。
5.6 常见泄漏模式代码审查清单
JS层:
-
useEffect返回的清理函数是否移除了事件监听器 -
setInterval/setTimeout是否在组件卸载时清除 - 全局
Map/Set/Array是否有大小上限和淘汰策略 - Promise是否在所有路径都有resolve/reject
- 异步回调是否持有已卸载页面对象的引用
Native/C++层:
-
napi_ref是否在所有路径(包括异常路径)都有对应的napi_delete_reference -
jsi::Value/jsi::Object是否被捕获到长生命周期的Lambda中 - TurboModule的Promise是否在所有路径都resolve/reject
- HostObject是否依赖JS GC释放关键Native资源
- 缓存容器是否有大小上限和淘汰策略
- 析构函数是否正确清理了所有跨线程引用
系统资源层:
- 文件句柄是否在异常路径也能正确关闭
- 网络连接是否设置了超时
- VSync/定时器是否在组件卸载时注销
- 图片解码缓冲区是否及时释放
6 经验总结
6.1 资源泄漏定位的核心原则
先判断泄漏发生在哪一层:通过AnonPage other和Native Heap的增长情况,初步判断是JS层、Native层还是跨层引用问题。
使用对应工具深入分析:JS层问题用React Native DevTools,Native层问题用DevEco Profiler Allocation,跨层问题需要两者结合。
关注跨层引用关系:RNOH应用中,JS、Native、ArkTS三层通过napi_ref、jsi::Value、HostObject等机制相互引用,泄漏往往发生在跨层引用链上。
修复后必须严格验证:泄漏修复后需要重复操作20次以上,触发GC,对比快照和Allocation,确认内存不再持续增长。
6.2 常见误区
| 误区 | 正确做法 |
|---|---|
| 内存上涨就认为是泄漏 | JS引擎和系统内存回收有延迟,短时间上涨可能只是GC延迟,需要多次操作后对比 |
| 只看总内存判断泄漏方向 | 需要同时看AnonPage other和Native Heap,单看总内存无法判断泄漏方向 |
| 快照看到Closure或GC roots就认为是虚拟机问题 | 这通常说明对象仍被JS引用链保留,需要展开保留器向上追到具体代码 |
| 只靠JS快照定位Native持有问题 | JS快照只能证明对象被持有,还需要用Allocation和代码调用链继续定位 |
| 修复后只看内存是否下降 | 必须重复操作20次以上,触发GC,对比快照和Allocation,确认泄漏真正消失 |
6.3 修复验证清单
- 重复操作验证:执行泄漏操作20次以上,确认内存不再阶梯式增长
- GC后验证:触发GC或等待2分钟后,确认内存回落到接近baseline
- 快照对比:对比修复前后的JS快照,确认泄漏对象不再出现
- Allocation对比:对比修复前后的Native Allocation,确认
Created & Existing数量不再增长 - 长时间运行:模拟真实使用场景运行30分钟以上,监控内存曲线是否平稳
- 多Instance验证:如果使用多RNInstance,反复创建/销毁Instance,确认内存回落
附录
附录A:常见泄漏根因速查表
| 泄漏现象 | 可能根因 | 关键代码位置 | 排查方向 |
|---|---|---|---|
| AnonPage other持续增长 | JS事件监听器未移除 | 业务JS代码 | 检查useEffect清理函数 |
| AnonPage other持续增长 | JS全局缓存无上限 | 业务JS代码 | 检查Map/Set/Array大小 |
| Native Heap + AnonPage other同步增长 | napi_ref未释放 | TurboModule/N-API代码 | 检查NapiRef生命周期 |
| Native Heap + AnonPage other同步增长 | CallbackWrapper未释放 | ArkTSTurboModule.cpp | 检查Promise是否resolve/reject |
| 仅Native Heap增长 | TurboModule缓存无淘汰 | TurboModuleProvider.h | 检查m_cache增长 |
| 仅Native Heap增长 | 图片URI缓存无上限 | ImageSourceResolver.h | 检查remoteImageSourceMap |
| 仅Native Heap增长 | Blob数据未释放 | BlobCollector.cpp | 检查JS Blob引用是否释放 |
| 仅Native Heap增长 | HostObjectProxy未释放 | JSVMRuntime.cpp | 检查JS HostObject引用 |
| 多Instance累积 | 静态单例未清理 | 多个静态单例 | 检查Instance析构清理路径 |
| VSync相关 | weak_ptr原始指针泄漏 | VSyncListener.cpp / UITicker.h | 检查VSync回调是否正常触发 |
附录B:HarmonyOS资源泄漏检测工具
| 检测手段 | 适用场景 | 工具 |
|---|---|---|
| hidumper内存查看 | 进程级内存趋势监控 | hdc shell hidumper --mem <pid> |
| DevEco Profiler Allocation | Native堆分配追踪 | DevEco Studio > Profiler > Allocation |
| React Native DevTools Memory | JS堆快照对比分析 | React Native DevTools > Memory |
| HiAppEvent系统事件 | 内存压力事件通知 | 系统回调onMemoryLevel |
| ASan/MSan | 内存错误检测 | 编译时开启AddressSanitizer |
附录C:RNOH关键静态单例
| 单例 | 数据 | 清理机制 | 泄漏风险 |
|---|---|---|---|
TurboModuleProvider::m_cache | 模块缓存 | 随Instance析构 | 无(Instance级) |
CallbackGlobalRegistry | 模块回调注册 | unregisterModule | 未调用时残留 |
RNOHMarker::listeners | 性能标记监听器 | notifyListeners时压缩 | 不notify时累积 |
ImageCacheMemoryCache | 图片内存缓存 | 内存压力回调清空 | 全局单例不析构 |
PerformanceMetricsRegistry | 性能指标 | removeRNInstanceMetrics | 未调用时残留 |
FreezeStackCollector | 冻屏栈缓存 | unregisterRuntime | 未调用时残留 |
附录D:系统资源泄漏常见场景
| 资源类型 | 泄漏场景 | 影响 |
|---|---|---|
| 文件句柄 | 打开后未关闭,异常路径跳过close | 句柄数耗尽,后续open失败 |
| 网络连接 | HTTP请求未设置超时,回调未清理 | 连接数耗尽,内存增长 |
| 图片解码缓冲区 | 大图片解码后未及时释放 | Native Heap快速增长 |
| VSync句柄 | 注册后未注销 | 持续触发回调,CPU和内存开销 |
| 定时器 | libuv Timer未取消 | 事件循环无法退出,内存残留 |
更多推荐

所有评论(0)