【发布时间】:2015-08-14 19:05:34
【问题描述】:
我有一个相当复杂的 Web 应用程序,它以极快的速度泄漏节点。使用 Windows 任务管理器,我可以看到 chrome 进程使用的内存量每秒增加约 3MB。
我最初的方法是禁用部分应用程序以隔离导致问题的区域。经过一些试验和错误发现原因是文档中的一些表更新。该应用程序每秒从服务器请求数据以更新多个数据表。每个表都以相同的方式更新。从表中删除所有行,并插入包含更新数据的新行。检查执行此操作的通用代码时,我没有发现问题。
为了解决这个问题,我显然求助于 Chrome 开发者工具。我遇到的问题是这些工具给我的信息相互矛盾。
- 时间轴工具显示节点数以每秒大约 28000 个的速度增加。强制垃圾回收不会将其还原到原来的水平。
- 时间轴工具显示 JS 堆大小随时间波动。强制垃圾回收将堆返回到其原始大小(正负数 100K)
- 使用“三快照”技术,Profiler - 堆快照工具显示快照 1 和快照 2 之间没有创建快照 3 中存在的 HTML 或文本节点。
- 比较快照显示了许多 HTMLTableRowElement、HTMLTableCellElement、HTMLInputElement 和 Text 节点的创建和删除。没有报告节点数增加。
- Profiler - 堆分配工具验证堆快照工具的结果。任何时候都不会报告任何节点类型的泄漏。
- 堆工具显示(编译代码)、(数组)和(系统)类型的小幅增加。
- Heap Profile 工具报告我的“原始”javascript 版本的应用程序的堆大小约为 12MB,而我的闭包编译器 - 编译版本的应用程序的堆大小约为 7MB。这些值不会随着时间的推移而增长太多。
这让我有点困惑。显然存在内存泄漏。 Windows 任务管理器和时间线工具将其报告为节点泄漏,但堆分析工具和 JS 堆时间线未显示此问题。
据我所知,HTMLTableRowElements 仅在两个地方被引用,在文档中和用于按值查找的对象中。清除表时始终清除对象。我可以通过更改我的代码来创建所有节点来“解决”这个问题,但从不将它们插入到文档中,只是在对象中引用它们。显然这不是一个修复,因为用户看不到数据。
经过 2 天的测试和调查,我现在不知道如何进行。如果你加入 IE 和 Firefox,情节就会变厚。这些浏览器似乎没有相同的内存/节点泄漏。我也相信 Chrome 以前不存在这个问题。不幸的是,似乎没有办法回到以前版本的 chrome 来查看它是否是 Chrome 中的错误。
有没有人对此有任何建议。我是否遗漏了一些东西,或者误解了开发人员工具的输出?有没有办法回到以前的 chrome 版本?这听起来像是 Chrome 中的错误吗?欢迎所有的cmets。
这是用于使用 Google 的 Closure API 插入项目的代码:
/**
* Inserts and/or updates a row in the table.
*
* @param {string|number} rowId The identifier of the row in the rows_ map
* @param {boolean} insertTop If true the row is inserted at the top of the document
* @param {...goog.dom.Appendable} var_args The items to add to each cell in the row
*/
sm.ui.DataTable.prototype.updateRow = function(rowId, insertTop, var_args) {
var dom = this.getDomHelper();
// Insert the new session data
if(!this.rows_[rowId]) {
// There is no row present (simple case of create one)
// Create the table row
var row = this.rows_[rowId] = dom.createDom('tr', {'style' : 'display: none;'});
var colView = this.colView_;
var colNames = this.columnNames_;
for(var i = 0; i < colNames.length; i++) {
var cell = dom.createDom('td');
if(!colView[i]) {
goog.style.showElement(cell, false);
}
row.appendChild(cell);
}
// Add to the table if element exists
var element = this.getElement();
var tBody = element.tBodies[0];
if(element) {
this.showPage_(this.currentPage_, false);
if(insertTop) {
// Insert a row at the top of the table
tBody.insertBefore(row, tBody.rows[0] || null);
} else {
// Append to the end if insert top no set true
tBody.appendChild(row);
}
this.showPage_(this.currentPage_, true);
// Update the footer to as it may need displaying or changing
this.updateFooter_();
}
}
// Loop over the var args and set the content of each cell
// arguments will be string, Node, array of strings and Nodes
for (var i = 0; i < arguments.length - 2; i++) {
var row = this.rows_[rowId];
dom.removeChildren(row.cells[i]);
dom.append(row.cells[i], arguments[i + 2]); // Removing this line "cures" the problem.
}
};
/**
* Removes all rows from the table.
* @param {string=} opt_message Message to display in the instead of data (reset table again to clear)
*/
sm.ui.DataTable.prototype.reset = function (opt_message) {
var element = this.getElement();
var tBody = element.tBodies[0];
while(tBody.rows.length > 0) {
tBody.deleteRow(0);
}
// Reset the rows and pages
this.rows_ = {};
this.currentPage_ = 1;
this.updateFooter_();
if(opt_message) {
// Create the row to inset in the table
// Ensure it spans all the columns
var dom = this.getDomHelper();
var messageCell = dom.createDom('tr', null,
dom.createDom('td', {'colspan' : this.columnNames_.length}, opt_message));
goog.dom.append(tBody,messageCell);
}
};
【问题讨论】:
-
你能分享一些代码吗?节点的创建和替换将是最有趣的。
-
@Hauke 我会尝试获取一些代码。我将不得不从应用程序中获取相关的 sn-ps,因为发布所有这些内容非常复杂。
-
dom.removeChildren 到底是做什么的?顾名思义,我认为它会删除单元格[i] 的子代,而不是单元格[i] 本身。对吗?
-
是的,这是正确的,该函数中的实际代码是:var child; while ((child = node.firstChild)) { node.removeChild(child); }
-
并且 append 将遍历字符串/节点数组并创建相应的 dom 元素?在开发工具中,您是否检查过此处提到的分离的 DOM 树条目:developer.chrome.com/devtools/docs/heap-profiling-dom-leaks?
标签: javascript google-chrome web-applications memory-leaks nodes