You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 11:03:18