我创建了一个simple Angular app,其中包含一个表格和一个表格(PrimeNg DataTable)。
为了在内存利用率和初始读数和最终读数方面有很大差异,我执行了多次Form Post调用(300次)并导航Table到第20页,每页1000行,还对所有列进行了5次排序,并取得了堆叠具有初始和最终状态的快照(在私有/隐身选项卡中禁用所有加载项,并在dev env中运行angular-cli app)。
在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();
});
}
.
.
.
.
}
是否是内存泄漏的情况,如果是,我究竟做错了什么,或者在大量使用应用程序后内存增加是正常行为?该应用程序的帧速率也在最后显着下降。
答案 0 :(得分:0)
这肯定是一个内存泄漏,但应用程序需要详细的分析,以便将导致此问题的问题归零。对于您的情况,我们可以看到堆大小增长但是为了知道究竟是什么导致泄漏或泄漏的位置......您需要扩展标识符并查看哪些对象仍保留在内存中(具有引用作为您的app的父节点)并且丢失了对父节点的引用。这将帮助您跟踪导致这些内存泄漏的代码片段(dom或jS)。
有很多好的做法可以防止你的应用程序泄漏那么多......你可以在这个article中找到一些(它适用于angularjs但适用相同的概念)。
我还发现在ngrx / store这样的单独状态包装器中隔离状态很有用,这样你就可以确定它不是你所提示的数据大小,你可以清楚地看到状态的大小对象以及明确且明确地改变(不是字面意思)或向状态对象添加东西。
此外,更改策略对内存堆的大小没有太大影响(如果它甚至确实影响它),但只是有助于在内存堆中保存程序额外检查以查看应用程序模型是否已更改使角度影响DOM的变化。但它使内存堆不受影响。因此它与明确的泄漏无关
展开每个标识符以查找哪些DOM节点已分离并仍保留在堆中!然后,您可以设计如何设计泄漏...例如使用ngIf或任何其他角度指令调用onDestroy(垃圾收集DOM节点或对象)在它们所附加的组件上。