Rxjs Map操作符创建新对象时的内存泄漏问题修复
内存泄漏问题排查与修复
核心问题分析
- 全量创建MapAsset对象:每次资产更新时,不管资产是否有变化,都会对所有资产调用
MapAsset.fromAsset生成新对象。哪怕仅单个资产更新,也会创建21000个新实例,按10条/秒的频率,几分钟就会累积数百万对象。 distinctUntilChanged完全失效:默认的distinctUntilChanged对比数组引用,但每次Array.from(assets.values())和assets.map(...)都会生成新数组,导致所有事件都会向下流,没有起到去重作用。shareReplay缓存无限制:默认配置会缓存所有发射值,旧数组和MapAsset对象被长期持有,无法被垃圾回收机制清理。
具体修复步骤
1. 复用未变化的MapAsset对象
维护一个缓存映射,仅对更新的Asset创建新MapAsset,未变化的直接复用旧对象:
// 维护缓存:key为asset唯一标识,value为对应MapAsset实例 private mapAssetCache = new Map<string, MapAsset>(); private destroy$ = new Subject<void>(); const assets$ = this.getAllAssets(); const mapAssets$ = assets$ .pipe( map(assets => { return assets.map(asset => { // 假设asset有唯一id字段,比如asset.id const existingAsset = this.mapAssetCache.get(asset.id); // 根据业务判断资产是否未发生变化,比如对比更新时间戳 if (existingAsset && asset.lastUpdated === existingAsset.lastUpdated) { return existingAsset; // 复用旧对象 } const newMapAsset = MapAsset.fromAsset(asset, transformMatrix); this.mapAssetCache.set(asset.id, newMapAsset); return newMapAsset; }); }), // 修改distinctUntilChanged,对比数组内对象引用而非数组引用 distinctUntilChanged((prev, curr) => { if (prev.length !== curr.length) return false; return prev.every((item, idx) => item === curr[idx]); }), // 限制缓存大小并启用引用计数,避免无限制缓存 shareReplay({ bufferSize: 1, refCount: true }), // 组件销毁时终止流 takeUntil(this.destroy$) ); // 辅助方法:判断Asset与对应MapAsset的数据是否一致 private isAssetUnchanged(asset: Asset, mapAsset: MapAsset): boolean { // 可根据实际业务扩展,比如对比多个关键字段 return asset.lastUpdated === mapAsset.lastUpdated; }
2. 修复getAllAssets中的distinctUntilChanged
原方法中数组每次都是新实例,导致去重失效,改为对比资产实际内容:
getAllAssets(): Observable<Asset[]> { return this.store.select(state => state.assetListState.assetData) .pipe( map(assets => Array.from(assets.values())), distinctUntilChanged((prev, curr) => { if (prev.length !== curr.length) return false; return prev.every(prevAsset => { const currAsset = curr.find(item => item.id === prevAsset.id); return currAsset && currAsset.lastUpdated === prevAsset.lastUpdated; }); }), shareReplay({ bufferSize: 1, refCount: true }), takeUntil(this.destroy$) ); }
3. 组件销毁时清理资源
在组件销毁时终止Observable流并清空缓存,确保资源被回收:
ngOnDestroy(): void { this.destroy$.next(); this.destroy$.complete(); this.mapAssetCache.clear(); // 清空MapAsset缓存 }
额外优化建议
- 减少不必要的数组转换:如果store中的
assetData是Map类型,可考虑直接传递Map而非转成数组,减少对象创建开销。 - 使用
trackBy优化DOM渲染:如果MapAsset用于列表渲染,在*ngFor中添加trackBy,避免Angular销毁重建未变化的DOM元素:
<div *ngFor="let asset of mapAssets$ | async; trackBy: trackById"> <!-- 渲染内容 --> </div>
trackById(index: number, asset: MapAsset): string { return asset.id; }
内容的提问来源于stack exchange,提问作者Patola
相关产品推荐
相关产品推荐

