【问题标题】:How to identify a memory leak in a very small Angular App如何识别非常小的 Angular 应用程序中的内存泄漏
【发布时间】:2019-04-12 09:08:18
【问题描述】:

我目前正在开发一个 Angular 应用程序,该应用程序应该 24/7 运行至少一个月(制造软件)。 客户端仅接受每月仅发生一次浏览器重启(维护间隔)。 我们实现的第一个用例只包含一个向用户显示一些信息的组件。 此时没有用户交互!信息从服务器推送到客户端。 目前我只是从服务器轮询数据更新并向用户显示信息。

当前200ms的间隔只是为了研究目的,在真实场景中是1000ms。 下面的代码在 Chrome 中在 3 小时内导致内存增加约 40MB,并且 cpu 使用率增加高达 50%(消耗两个核心之一)。

推送通知的目标技术是 SignalR。 由于我使用 SignalR 发现了内存问题,因此此处提供的轮询实现用于调查 SignalR 库是否是问题所在。 不幸的是,我在这里遇到了同样的问题。

当然,每 30 分钟执行一次 window.location.reload() 可以“解决”问题,但这不是一个好的解决方案。 如果我在 3 小时后执行重新加载,页面就会崩溃,Chrome 会显示“哦,不……崩溃”。 我正在使用 Chrome 73 和 Edge,Edge 的内存增加明显高于 Chrome。 使用 Angular 7.2

<div *ngIf="info" style="width: 100%; height: 100%; margin: 0 auto;">
  <div class="info status{{ info.Status }}">
    <div class="location"><p class="font2">{{ info.Location }}</p></div>
    <!-- ... further div elements here but no other *ngIf or *ngFor -->

        <div *ngIf="info?.Details" class="no-padding">
          <div class="column1 no-padding" *ngFor="let item of info.Details">
            <div class="inverse"><p class="font1">{{ item.Name }}</p></div>
          </div>
        </div>
        <img class="icon" src="assets/Icon{{info.Icon}}.png"/>
    </div>
  </div>
</div>
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription, interval, Subject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Info } from '../data/info';

@Component({
  selector: 'app-info',
  templateUrl: './info.component.html',
  styleUrls: ['./info.component.scss']
})
export class InfoComponent implements OnInit, OnDestroy {
  subscribetimer: Subscription;
  subscribelistener: Subscription;
  listener: Subject<Info>;
  info: Info;

  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.listener = new Subject<Info>();
    this.subscribelistener = this.listener.subscribe(unit => this.info = unit);

    this.subscribetimer = interval(200)
      .subscribe(data => {
        this.http.get<Info>(`http://localhost:5000/poll`)
            .subscribe(res => this.listener.next(res));
      });
  }

  ngOnDestroy() {
    this.subscribetimer.unsubscribe();
    this.subscribelistener.unsubscribe();
  }
}

我希望我可以 24/7 全天候运行这个小型应用程序至少一个月,而不会出现内存和 CPU 消耗问题。

【问题讨论】:

  • 将http响应推送到主题有什么特殊原因吗?
  • 实际上,在这种情况下不是,主题是从 ng2-signalr 库使用中遗留下来的。您对使用主题有任何顾虑吗?
  • @M4n1,也检查一下这个"How to detect rxjs related memory leaks in Angular apps" 问题——它提到了一些有用的工具。

标签: angular rxjs angular-httpclient


【解决方案1】:

目前尚不清楚泄漏的是什么(以及是否泄漏),因此很难给出具体的建议。但这里有一些提示:

1) 尝试删除不必要的Subjects,您可以将一个可观察的info$ 暴露给视图:

export class AppComponent  {
  info$: Observable<IData>;

  constructor(private http: HttpClient) { }

  ngOnInit() {
    this.info$ = interval(200)
      .pipe(
        exhaustMap(() =>
          this.http.get<Info>(`http://localhost:5000/poll`)
        )
      );
  }
}

在视图中,类似于:

<div *ngIf="info$ | async as info">
  <div *ngFor="let item of info.items">
    {{item}}
  </div>
</div>

2) 您可能在 http-get 中有大量超时,例如您的计时器每200ms 计时,http-get 可能需要500msexhaustMap 将处理背压,但您应该添加 timeout 以限制请求时间并肯定添加一些错误处理,因为 http-get 会出现错误。一个非常基本的例子:

this.http.get<Info>(`http://localhost:5000/poll`).pipe(
  // killing requests taking too long
  timeout(400),
  // some error handling logic should go here
  catchError(error => {
    return EMPTY;
  })
)

更复杂的方法可能是 timeoutretry

除此之外,http 响应本身可能是错误的或者是非 json 的,这会导致错误。所以这里的错误处理是必须的。

这是more detailed overview of error handling in rxjs

Stackblitz example for above said.

旁注:您不知道在一个月 24/7 全天候运行时会发生什么。所以你肯定也想在你的系统中添加一些日志记录。只是为了能够学习,如果失败了。

【讨论】:

  • 我现在有一些时间进行调查,我也尝试了您的建议。不幸的是,它并没有解决问题。我还减少了每秒的通知数量。我认为使用*ngIf{{value}} 并不是最好的选择。我发现了这个 [链接]stackoverflow.com/questions/43034758/… 问题。我试图将*ngIf 更改为[hidden]{{value}} 以进行数据绑定。首先它变得更好,但几个小时后,同样的问题。根据 Chrome 堆分析器,似乎只剩下 Zone 对象实例。
  • @M4n1,感谢您的回复和反馈!所以它可能不是Rx,而是Zone wrapper?只是为了确定:您是否在prod mode 中运行?唉,我不熟悉分析 zone.js 的工具。仅供参考,"memory leak" 的 Angular 有一些未解决的问题——这很难挖掘,但你可能会在那里找到见解。
【解决方案2】:

根据rxjs docs

interval 返回一个 Observable,它发出无限的升序整数序列,在这些发射之间有一个恒定的时间间隔供您选择。第一个发射不会立即发送,而是在第一个周期过去之后发送。默认情况下,此运算符使用异步 SchedulerLike 来提供时间概念,但您可以将任何 SchedulerLike 传递给它。

我认为问题出在这里:

this.subscribetimer = interval(200)
  .subscribe(data => {
    this.http.get<Info>(`http://localhost:5000/poll`)
        .subscribe(res => this.listener.next(res));
  });

基本上,每个200ms 都会创建一个新的subscribe 函数。这些对象很可能永远不会被垃圾处理,因此它们会增加内存消耗。

我建议在收集到响应后查看代码以正确退订。

或者,如果您可以控制 API 服务器,我肯定会使用 Web 套接字。看看socket.io,它让启动一个简单的套接字服务器变得非常简单。

【讨论】:

  • 看看下面这个问题,http observables are self-terminating:stackoverflow.com/questions/35042929/…
  • 对该问题的第二个最多支持的答案(32 票)表明最好取消订阅。此外,在 OP 的代码中,有 两个 订阅函数,第一个 (interval(200).subscribe) 不是 http observable,所以我仍然强烈建议明确取消订阅并检查内存是否仍然被填满。
  • 你是对的,这不是最好的主意。我现在已将其更改为使用@Kos 所暗示的.pipe(exhaustMap(...)),但这并不能解决问题。 Chrome 堆分析器指示仅 Zone 对象留在内存中。知道为什么它们从不清理吗?
  • 你能用修改后的代码更新答案中的代码吗?
猜你喜欢
  • 2016-11-23
  • 1970-01-01
  • 2012-09-22
  • 2011-03-28
  • 2013-03-19
  • 2014-01-06
  • 1970-01-01
  • 2014-04-19
相关资源
最近更新 更多