如何在Node.js / V8中调试/分析极长的GC暂停

时间:2015-01-13 19:13:20

标签: javascript node.js memory-leaks garbage-collection v8

我正在尝试分析相对复杂的Node.js服务器应用程序中的内存/ GC问题。即使在非常适中的负载下,它也会在明显的时期内变得无法响应,并且随着时间的推移这些停顿会变得更长。使用--trace-gc参数运行表明可能导致极长的垃圾回收时间:

[4805]      537 ms: Mark-sweep 17.6 (46.4) -> 10.3 (47.4) MB, 20 ms [allocation failure] [GC in old space requested].
[4805]     1338 ms: Mark-sweep 31.3 (58.4) -> 19.2 (57.2) MB, 40 ms [allocation failure] [promotion limit reached].
[4805]     2662 ms: Mark-sweep 58.0 (79.2) -> 43.9 (85.2) MB, 109 ms [Runtime::PerformGC] [promotion limit reached].
[4805]     4014 ms: Mark-sweep 90.1 (111.5) -> 70.6 (113.9) MB, 114 ms [allocation failure] [promotion limit reached].
[4805]     7283 ms: Mark-sweep 129.7 (153.9) -> 112.0 (158.9) MB, 511 ms [allocation failure] [promotion limit reached].
[4805]    10979 ms: Mark-sweep 184.6 (210.9) -> 160.3 (212.9) MB, 422 ms [Runtime::PerformGC] [promotion limit reached].
[4805]  1146869 ms: Mark-sweep 243.8 (271.4) -> 191.6 (267.9) MB, 1856 ms [allocation failure] [promotion limit reached].
[4805]  1731440 ms: Mark-sweep 282.1 (307.4) -> 197.5 (298.9) MB, 1 / 11230 ms [allocation failure] [promotion limit reached].
[4805]  2024385 ms: Mark-sweep 291.0 (320.8) -> 197.3 (306.9) MB, 9076 ms [Runtime::PerformGC] [promotion limit reached].
[4805]  2623396 ms: Mark-sweep 290.9 (317.1) -> 196.9 (311.9) MB, 1 / 15401 ms [allocation failure] [promotion limit reached].
[4805]  3223769 ms: Mark-sweep 291.4 (323.6) -> 187.8 (318.9) MB, 1 / 13385 ms [allocation failure] [promotion limit reached].
[4805]  4225777 ms: Mark-sweep 280.1 (324.2) -> 190.6 (315.9) MB, 1 / 13266 ms [allocation failure] [promotion limit reached].
[4805]  4705442 ms: Mark-sweep 286.2 (321.4) -> 195.2 (314.9) MB, 1 / 17256 ms [Runtime::PerformGC] [promotion limit reached].
[4805]  5225595 ms: Mark-sweep 288.3 (324.0) -> 201.7 (316.9) MB, 1 / 22266 ms [Runtime::PerformGC] [promotion limit reached].
[4805]  6127372 ms: Mark-sweep 296.5 (324.6) -> 200.5 (316.9) MB, 1 / 28325 ms [allocation failure] [promotion limit reached].
[4805]  6523938 ms: Mark-sweep 297.8 (328.9) -> 198.8 (323.9) MB, 1 / 27213 ms [allocation failure] [promotion limit reached].
[4805]  7355394 ms: Mark-sweep 292.1 (330.7) -> 223.9 (322.9) MB, 60202 ms [allocation failure] [promotion limit reached].

可以找到完整的(--trace-gc-verbose)输出here

这些日志是使用以下参数运行服务器的结果:

--expose-gc --trace-gc --trace-gc-verbose --trace-gc-ignore-scavenger --max-old-space-size=1000

运行的时间越长,停顿时间越长(通常为几分钟),直到最后几小时后它完全锁定。可用内存永远不会用完,RSS甚至没有接近1000mb旧空间限制,因此它似乎不是泄漏。在我看来,在代码中可能会有一些相当不寻常的东西,使它变得非常困难"让GC在可接受的时间范围内完成工作。

我的问题是:我如何进一步分析这个问题,并缩小可能的原因?任何可以推荐的工具来帮助解决这样的问题?我本质上寻找一种比天真地关闭和部分代码更有效的方法,这非常麻烦且耗时。

顺便说一下,我非常感谢任何文档的链接,这些文档解释了GC调试输出中使用的术语/消息(例如"达到促销限制"),以及那里列出的数字。我对V8 GC的工作原理有了一个非常基本的了解(this帮助了很多),但大部分输出仍然超出了我的范围。

如果重要:这是在Ubuntu 14.04服务器上的Node.js v0.10.33上运行。

修改 前段时间我们转到了io.js,这个问题根本不再发生(可能是由于最近的V8版本)。我从来没有找到Node v0.10这个问题的原因,更不用说修复了。

2 个答案:

答案 0 :(得分:4)

您是否能够在单个节点上重现该问题?我想如果我遇到这种情况,我可能会混合使用以下内容:

  • 编写一个允许我在本地实例上复制的加载器
  • 如果没有,请在prod中放置一个将接收流量子集的实例,并修改它以执行以下操作
  • node-heapdump添加到您的来源,每隔一段时间调用它,并以N分钟为间隔将结果导出到json文件。
  • 如果您在本地运行,您可能也可以利用memwatch
  • 等待慢速GC启动。
  • 在您知道GC开始缓慢的时候,抓住一些堆转储。
  • 将它们加载到chrome中并使用three snap shot technique进行分析(我想你可以在我们的例子中称之为N快照技术)

基本上,您将加载堆并开始浏览它们以尝试了解堆叠的东西是什么类型,持有它的是什么,因此您将了解GC为什么需要这么长时间

  

可用内存永远不会用完,RSS甚至没有接近1000mb旧空间限制,所以它似乎不是泄漏。在我看来,在代码中可能会有一些相当不寻常的东西,使它变得非常困难"让GC在可接受的时间范围内完成工作。

在这里,您可能正在寻找长而圆形的保留树。但是在一天结束的时候,即使是这样,你也应该能够确定树的根是什么,它的内容是什么,并尝试减少删除的方法。

我也同意@dandavis并怀疑关闭。

答案 1 :(得分:0)

这个答案可能没有您想要的具体,但我建议您查看沃尔玛good package框架中的hapi.js。它可以很好地将日志记录扩展到--trace-gc之外。它是一个侦听以下一个或多个事件的进程监视器:

  • ops - 系统和流程性能 - CPU,内存,磁盘和其他指标。
  • response - 有关传入请求和响应的信息。这映射到hapi服务器发出的“响应”或“尾部”事件。
  • log - 未绑定到特定请求的日志信息,例如系统错误,后台处理,配置错误等。映射到hapi服务器发出的“log”事件。
  • error - 请求状态代码为500的响应。这会映射到“请求错误”hapi事件。
  • request - 请求记录信息。这会映射到通过request.log()发出的hapi'request'事件。

您必须提取Hapi库才能使其正常工作,但出于调试目的,暂时可能值得。总的来说,我强烈推荐Hapi用于扩展Node.js应用程序,沃尔玛的这些人在过去一年中一直在做着惊人的事情。