Dom*_*omi 5 javascript profiling memory-leaks v8 node.js
查找内存泄漏是一项非常困难的任务,尤其是在涉及使用许多第三方库的现代 JS 代码时。
例如,我目前在 rollup 中面临内存泄漏,涉及 babel 和自定义 babel 插件。
我正在探索几种常见的策略来追捕它们:
node --heap-prof node_modules/rollup/dist/bin/rollup -c分析汇总构建)。然后在 Chrome Dev Tools 中打开它,通过Memory-> Load。最后一个问题也是我在这里的问题:我们如何分析 Node(或一般的 V8)中的对象分配树?我怎样才能找出我在步骤 (2) 中识别的对象在哪里踢?
通常,这个问题的答案会告诉我们在哪里更改代码以阻止泄漏。(当然,如果您的问题是内存流失,而不是内存泄漏,那么这个问题可能不是那么重要。)
在我的例子中,我知道内存被 Babel AST 节点和路径对象占用,但我不知道它们为什么会徘徊,即我不知道它们存储在哪里。如果你只是自己运行 Babel,你可以验证它不是 Babel 泄漏内存。我目前正在尝试各种技巧来找出它们的存储位置,但仍然没有运气。
遗憾的是,到目前为止,我还没有找到任何工具来帮助解决问题 (3)。即使相关有深度的文章(比如这和这里的slidedeck)手动绘制了堆分配步骤。感觉好像没有这样的工具,还是我错了?如果没有工具,也许在某个地方有关于这个的讨论?
请注意,虽然您不必在 JS 中明确地释放内存,但仍然可能出现内存泄漏。同时,Node 内存分析实用程序(几乎是犯罪)记录不足。让我们了解如何使用它们。
TLDR:跳到下面带有示例的动手部分,标题为“查找内存泄漏(带有示例)”。
由于 JS 有GC,内存泄漏只有几个可能的原因:
您正在使用(“保留”)不再使用的大对象,通常在文件或全局范围内的变量中。这要么是偶然的,要么是简单(不确定)缓存方案的一部分:
let a;
function f() {
a = someLargeObject;
}
Run Code Online (Sandbox Code Playgroud)
有时对象在保留的闭包中徘徊。例如:
let cb;
function f() {
const a = someLargeObject; // `a` is retained as long as `cb`
cb = function g() {
eval('console.log(a)');
};
}
Run Code Online (Sandbox Code Playgroud)
您可以通过从不存储到或手动清除这些变量来轻松修复此类内存泄漏。主要的困难是找到这些挥之不去的物体。
首先,Node.js 和 Chrome 都使用相同的 JS 引擎:v8。因此,Chrome 开发工具团队添加 Node 调试和分析支持是可行的。虽然还有其他可用的工具,但 Chrome Dev Tools (CDT) 可能更成熟(并且资金可能更好),这就是我们(目前)将重点放在如何使用 Chrome Dev Tools 进行 Node 内存分析和调试的原因。
使用 CDT 分析 Node 内存的主要方法有两种:
--heap-prof以生成堆配置文件日志文件。然后在 CDT 中加载并分析日志。--inspect/--inspect-brk标志运行您的应用程序,以便在 CDT 中调试您的 Node 应用程序。然后只需根据自己的喜好使用 CDT 的Memory选项卡(此处的文档)。heap-prof运行您的应用程序--heap-prof以生成堆配置文件日志文件。然后在 CDT 中加载并分析日志。
heap-prof启用的情况下运行您的应用程序。例如:node --heap-prof app.js
heap-prof相关的命令行标志进一步自定义它。Heap*.heapprofile.Load? 选择Heap*.heapprofile这一步允许您首先验证内存泄漏,并找出可能导致它的分配或对象类型。
我们来看看 CDT 的内存分析工具。它具有三种模式:
可悲的是,记录的日志--heap-prof仅包含模式1的数据。但是,这种模式不足以回答OP的第三个问题:如何找出分配的对象仍然存在的原因/位置(即:未使用后“保留”不再)?
如选项卡中所述:回答该问题需要第二种模式。
不知道有没有隐藏的修改Node的profile模式的方法,不过我没找到。我尝试了一些事情,包括从这个未记录的Node.js CLI 标志列表中添加。
inspect/inspect-brk使用--inspect/--inspect-brk标志运行您的应用程序,以便在 CDT 中调试您的 Node 应用程序。然后只需根据自己的喜好使用 CDT 的Memory选项卡(此处的文档)。
node --inspect-brk app.jschrome://inspect在 Chrome 中打开。--max-old-space-size=4096(或更大的数字)来加倍默认值。或者,更好的是,如果可能,简化您的测试用例以使用更少的内存并加快分析速度。按照方法 2 的步骤操作后,您现在正在查看查找泄漏所需的所有信息。
让我们看一些基本的例子:
下面的代码举例说明了一个简单的内存泄漏:文件范围a永远存储数据。
let a;
function test1() {
const b = [];
addPressure(N, b);
a = b;
gc(); // --expose-gc
}
test1();
debugger;
Run Code Online (Sandbox Code Playgroud)
笔记:
gc在分析时调用的原因。通过这种方式,我们可以确保摆脱所有可收集的引用,并明确关注“挥之不去”的对象。
expose-gc标志gc();例如:node --inspect-brk --expose-gc app.js一旦断点命中,我就停止录制并得到以下信息:
Constructor视图列出了所有延迟对象,按构造函数/类型分组。
Shallow Size或排序Retained Size(两者都在此处解释)string占用了大部分内存。让我们打开它。
Constructor,您可以找到所有单个对象的列表。第一个(最大的)对象通常是罪魁祸首。选择第一个。Retainers视图现在向您显示该对象仍被保留的位置。
Retainers视图上的文档不是很完整。这就是我尝试导航它的方式,直到它吐出我正在寻找的代码行:
我的发现显示在此屏幕截图中:
我们看到三个函数在这个对象的挥之不去中起作用:
gc- 我不知道为什么会这样。可能与GC内部有关。可能是因为它gc会缓存对某些(如果不是全部)延迟对象的引用。addPressure函数分配的对象。这也是保留它的引用的来源。test1函数是我们将对象分配给文件范围的a.
a,或者确保我们a在不再使用它后清除它。我希望,这可以帮助您开始寻找和消除内存泄漏的激动人心的旅程。请随时在下面询问更多信息。
| 归档时间: |
|
| 查看次数: |
511 次 |
| 最近记录: |