减少 Angular 应用程序的内存使用率

Posted

技术标签:

【中文标题】减少 Angular 应用程序的内存使用率【英文标题】:Reducing memory utilization of Angular app 【发布时间】:2018-06-22 22:25:14 【问题描述】:

我创建了一个simple Angular app,其中包含一个表单和一个表格 (PrimeNg DataTable)。

只是为了在初始读数和最终读数的内存利用率上获得显着差异,我执行了多次 Form Post 调用(300 次)并将表格导航到第 20 页,每页有 1000 行,还对表格的所有列进行了 5 次排序并取了具有初始和最终状态的堆快照(在私有/隐身选项卡中禁用所有附加组件,并且在开发环境中运行 angular-cli 应用程序)。

在 chrome 中,堆内存大小从 55 MB 增加到 146 MB(增加了 91 MB)

在 Chrome 中,堆内存大小从 23.16 MB 增加到 137.1 MB(增加了 113.95 MB)

当我的组件销毁时,我将取消订阅所有订阅(这将不起作用,因为这是一个单独的组件),我还将 changeDetectionStrategy 设置为 onPush。

app.component.html:

<div [ngBusy]="busy: busy, message: 'Loading data, this may take few minutes!'"></div>
<div style="width:50%">
  <form [formGroup]="taskForm" (ngSubmit)="addTask(taskForm.value)">
    <div class="width:100%;float:left; clear:both;">
      <div style="width:20%;float:left">
        <input type="text" [formControl]="index" class="form-control" placeholder="index" />
      </div>
      <div style="width:20%;float:left">
        <input type="text" [formControl]="name" class="form-control" placeholder="name" />
      </div>
      <div style="width:20%;float:left">
        <input type="text" [formControl]="userId" class="form-control" placeholder="userId" />
      </div>
      <div style="width:20%;float:left">
        <input type="text" [formControl]="mobile" class="form-control" placeholder="mobile" />
      </div>
      <div style="width:20%;float:left">
        <input type="date" [formControl]="taskDate" class="form-control" placeholder="taskDate" />
      </div>
    </div>
    <div class="width:100%;float:left; clear:both;">
      <button type="submit" class="btn btn-success">Add Task</button>taskPostCount
    </div>
  </form>
  <code *ngIf="addTaskResponse">addTaskResponse | json</code>
</div>

<div style="text-align:center">
  <p-dataTable [dataKey]="'_id'" *ngIf="isTableVisible()" [value]="table" expandableRows="true" (onFilter)="onColumnFilterChanged($event)"
    (onSort)="onTableColumnSortChanged($event)" [lazy]="true">
    <p-column field="index" header="Index" [filter]="true" [sortable]="true"></p-column>
    <p-column field="name" header="Task Name" [filter]="true" [sortable]="true"></p-column>
    <p-column field="mobile" header="Mobile" [filter]="true" [sortable]="true"></p-column>
    <p-column field="taskDate" header="Task Date"></p-column>
    <p-column field="createdAt" header="Date Created"></p-column>
    <p-column field="updatedAt" header="Date Updated"></p-column>
  </p-dataTable>
  <p-paginator [rowsPerPageOptions]="[10,20,30,50,100,200,300,400,500,1000]" [first]="tableSearchConfig.firstRowIndex" [totalRecords]="tableSearchConfig.totalRecords"
    *ngIf="isTableVisible()" [rows]="tableSearchConfig.rowsPerPage" (onPageChange)="onPageChanged($event)"></p-paginator>
</div>

app.component.ts:

@Component(
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
  providers: [AppService, TimeFromNow],
  changeDetection: ChangeDetectionStrategy.OnPush
)
export class AppComponent 
  // Properties declarations
  private unsubscribe: Subject<boolean> = new Subject<boolean>();

  constructor(private appService: AppService, private timeFromNow: TimeFromNow, private ref: ChangeDetectorRef, fb: FormBuilder) 
    this.taskForm = fb.group(
      'index': ['', Validators.compose([])],
      'name': ['', Validators.compose([])],
      'userId': ['', Validators.compose([])],
      'mobile': ['', Validators.compose([])],
      'taskDate': ['', Validators.compose([])],
    );
    this.index = this.taskForm.controls['index'];
    this.name = this.taskForm.controls['name'];
    this.userId = this.taskForm.controls['userId'];
    this.mobile = this.taskForm.controls['mobile'];
    this.taskDate = this.taskForm.controls['taskDate'];
    this.setTableSearchConfig();
  

  ngOnInit() 
    this.addBulkTasksOnLoad();
  

  ngOnDestroy() 
    this.unsubscribe.next(true);
    this.unsubscribe.complete();
    this.unsubscribe.unsubscribe();
  

  addBulkTasksOnLoad() 
    this.busy = this.appService.addTaskOnLoad().subscribe((res: any) => 
      this.loadTable();
    , (err: any) => 
    );
  

  addTask(taskForm: any) 
    this.taskPostCount++;
    this.appService.addTask(taskForm).takeUntil(this.unsubscribe).subscribe((res: any) => 
      this.addTaskResponse = res;
    ,
      err => 
        this.addTaskResponse = err;
      );
  

  loadTable(paginateEvent?: PaginateEvent, sortEvent?: SortEvent, filterEvent?: FilterEvent) 
    this.appService.getTable(this.tableSearchConfig).takeUntil(this.unsubscribe).subscribe((res: any) => 
      for (const history of res.data) 
        history.updatedAt = this.timeFromNow.transform(history.updatedAt);
      
      this.table = res.data;
      this.setTableSearchConfig(paginateEvent, sortEvent, filterEvent, this.tableSearchConfig.pageNumberToRequest, res.totalRecords);
      this.ref.detectChanges();
    );
  

  .
  .
  .
  .

这是内存泄漏的情况,如果是,我到底做错了什么,或者在大量使用应用程序后内存增加是正常行为?该应用程序的帧速率在最后也显着下降。

【问题讨论】:

【参考方案1】:

这绝对是内存泄漏,但应用程序需要详细分析以将导致此问题的问题归零。对于您的情况,我们可以看到堆大小增加,但为了知道究竟是什么导致了泄漏或泄漏在哪里......您需要扩展标识符并查看哪些对象仍保留在内存中(参考作为您的应用程序的父节点)并且丢失了对父节点的引用。这将帮助您追踪导致这些内存泄漏的代码片段(dom 或 JS)。

有很多好的做法可以防止你的应用泄露那么多......你可以在这个article中找到一些(它适用于 angularjs,但同样的概念适用)。

我还发现在单独的状态包装器(如 ngrx/store)中隔离状态很有用,这样您就可以确定它不是您正在提示的数据的大小,因为您可以清楚地看到状态的大小对象以及明确而明确的变异(不是字面意思)或向状态对象添加东西。

此外,更改策略不会对内存堆的大小产生太大影响(如果它确实影响它的话),只是有助于在内存堆中保存程序额外检查以查看应用程序模型是否已更改使角度影响 DOM 中的变化。但它使内存堆保持不变。所以它与明显的泄漏无关

扩展每个标识符以查找哪些 DOM 节点已分离并仍保留在堆中!然后您可以设计如何针对泄漏进行设计...例如在附加组件上使用 ngIf 或任何其他调用 onDestroy(垃圾收集 DOM 节点或对象)的角度指令。

【讨论】:

以上是关于减少 Angular 应用程序的内存使用率的主要内容,如果未能解决你的问题,请参考以下文章

内存使用率高 - 应用程序响应慢:已用内存值未减少 + 可用内存值未增加

减少“代码”内存使用量

如何减少 Cobalt 的内存使用量

UIPageControllerView 回收页面减少内存使用

减少 .NET 应用程序的内存使用?

使用太多内存来减少图像尺寸......有没有办法使用更少的内存来做到这一点?