我创建了一个简单的 Angular 应用程序,其中包含一个表格和一个表格(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();
});
}
.
.
.
.
}
这是内存泄漏的情况吗?如果是,我到底做错了什么,或者在大量使用应用程序后内存增加是正常行为?该应用程序的帧率最后也下降了很多。
这绝对是内存泄漏,但应用程序需要详细分析才能将导致此问题的问题归零。对于您的情况,我们可以看到堆大小增加但是为了知道到底是什么导致泄漏或泄漏在哪里......您需要扩展标识符并查看哪些对象仍保留在内存中(参考作为您的应用程序的父节点)并且丢失了对父节点的引用。这将帮助您追踪导致这些内存泄漏的代码片段(dom 或 JS)。
有很多好的做法可以防止你的应用程序泄漏那么多......你可以在这篇文章中找到一些(它适用于angularjs,但适用相同的概念)。
我还发现将状态隔离在像 ngrx/store 这样的单独状态包装器中很有用,这样您就可以确定它不是您正在提示的数据的大小,因为您可以清楚地看到状态对象的大小以及清楚明确地改变(不是字面上的)或向状态对象添加东西。
此外,更改策略不会太大地影响(如果它甚至根本不会影响)内存堆的大小,但只是帮助保存程序在内存堆中进行额外检查以查看应用程序模型是否已更改导致角度影响 DOM 的变化。但它使内存堆保持不变。所以它与明显的泄漏无关
展开每个标识符以查找哪些 DOM 节点已分离并仍保留在堆中!然后你可以设计如何针对泄漏进行设计......比如使用 ngIf 或任何其他在它们所连接的组件上调用 onDestroy(垃圾收集 DOM 节点或对象)的角度指令。