Table of Contents

1. 前言

本文是系列文章Node.JS Profile的一部分,完整的文章列表请去总章查看。

本文是Node内存相关文章的其中一篇,主要负责介绍V8引擎GC相关的详细技术内容。

2. GC日志

Node的V8引擎支持使用命令行添加option启动的形式,在stdout中输出GC相关内容的日志。GC日志基本上可以说是了解V8 GC行为及状态的唯一方法。

2.1 日志启用

GC日志需要在命令行启动node的时候添加option来启用,相关的option我找到一个不错的gist,里面有描述,这里fork了下:

而本文撰写时参照的Node版本,支持的GC相关option如下:

2.2 如何Runtime触发GC

如果想要在runtime使用代码主动触发GC,则需要在node启动的时候添加option--expose-gc

然后在代码里:

if (global.gc) {
    global.gc();
}

2.3 常用GC options输出效果范例

范例效果:

2.4 GC日志图解

范例中的图片来自:Are your v8 garbage collection logs speaking to you?Joyee Cheung -Alibaba Cloud(Alibaba Group)

options

–trace_gc

–trace_gc_nvp

–trace_gc_verbose

3. GC日志解析工具

3.1 aliyun-node/v8-gc-log-parser

阿里的一个GC日志解析工具,可以在命令行下运行,也可以在代码runtime里运行。该工具的主要作用是将node --trace_gc输出的内容进行可读化解析,输出成JSON格式,方便后续存储或分析。

当前版本支持的options有:

  • –trace_gc
  • –trace_gc_nvp
  • –trace_gc_verbose

该工具的最大优势就是内容完全按v8官方输出的来,没有作者主观性的内容增删,仅只是辅助阅读。

缺点是gc的日志输出就在stdout里,如果stdout还有点别的什么正常业务输出,分析起来就麻烦了,先要过滤一次,否则该工具用不了。

3.2 dainis/node-gcstats

第三方的GC信息工具,使用C++编写,作为node的addone运行,只能在代码runtime里运行。输出的内容和官方gc日志的内容不尽相同,相对来说更简洁,适合需要简单功能的用户使用。

优势在于可以在运行时直接获取gc信息(GC时触发监听事件),方便在代码里直接存储到其他地方,事后进行分析。

缺点是输出信息不如官方的gc日志那么详细和多样性。

3.3 marcominetti/node-memwatch

第三方的GC信息工具,使用C++编写,作为node的addone运行,只能在代码runtime里运行。这个工具关注的不仅仅只是GC,有几个功能有点意思:

  • 监听GC事件,并给出简单报告,功能比较类似dainis/node-gcstats
  • 监听最近5次GC,持续内存上涨则触发一个内存泄露的监听事件;可以和heapdump配合,监听到leak则导出heapdump
  • HeapDiff,用来对比并计算出两次堆快照的差异,意义不大

更详细的介绍可以看这篇:Node.js 性能调优之内存篇(三)——memwatch-next

优点基本同上面的那个工具,且可以判断leak(其实自己也可以实现)。

缺点是功能相对来说意义不是很大,输出的内容信息不足。

3.4 阿里工具的辅助脚本

aliyun-node/v8-gc-log-parser只支持事后解析静态日志文件并输出结果,但有的时候我们需要查看下实时的情况。这里有一个脚本可以使用:

该脚本暂时不能分析--trace-gc --trace-gc-verbose的输出结果,因为该输出是多行的,需要聚合后才可以发给解析工具。有兴趣的可以自己改造下。

4. NVP日志详解

在V8官方输出的GC日志当中,--trace_gc--trace_gc_verbose输出的内容都是比较简单的,主要给出了非常实用的:

  • gc类型
  • 暂停时间
  • 对象数量变化
  • 内存用量变化

这些信息基本上能满足大部分的使用情况。

--trace_gc_nvp这个option输出的信息则不同,相对于上述的两个option,nvp日志给出的内容相当详细,而且根据GC的类型不同,输出的内容也不尽相同。是用来进行GC细节调优时候非常重要的信息来源。

但可惜的是网上并没有很详细的,针对这个类型输出日志内容的解释,无论是官方的还是社区的。因此最后的手段就是阅读V8的源代码查找里面的信息细节。本文会在当前的章节段落中,将阅读源码获取的解释记录在下面,方便读者查阅。

当然这里会查找源码去寻求解释的也只是一部分比较有价值的内容,并非全部,如果读者有兴趣可以自行查阅源码,下面会给出源码信息。

4.1 源码阅读指引

行文使用的Node V8版本信息:

$ node -e “console.log(process.versions.v8)”
6.0.286.52

因此这里使用的V8分支是:6.0.286

源码主要是V8的:heap.ccgc-tracer.cc,这两个文件。

gc-tracer.h其实很简单,只是一些显示和输出之类的,主要的逻辑和统计还是在heap.h里。

几个结构和逻辑入口:

heap.h

gc-tracer.h

gc-tracer.cc

global-handles.h

global-handles.cc

4.1 Event::SCAVENGER

  • pause:GC暂停时长,单位ms
  • mutator:这里,关于什么是mutator,见这里
  • gc:GC类型字符串,这里肯定是s
  • reduce_memory:Memory reduction flag set. 只是一个布尔值的flag,见这里这里
  • heap.*:Amounts of time spent in different scopes during GC. 见这里这里
  • scavenge:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_SCAVENGE(新生代)
  • evacuate:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_EVACUATE(新生代)
  • old_new:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_OLD_TO_NEW_POINTERS(新生代)
  • weak:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_WEAK(新生代)
  • roots:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_ROOTS(新生代)
  • code:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_CODE_FLUSH_CANDIDATES(新生代)
  • semispace:类似上面的heap.*,按scope分的耗时,实质上是指:Scope::SCAVENGER_SEMISPACE(新生代)
  • steps_count:增量标记步骤数,见这里(老生代,MC是指Mark & Compact)
  • steps_took: 增量标记步骤,见这里(老生代,MC是指Mark & Compact)
  • scavenge_throughput:scavenge回收速率,bytes/ms,见这里,方法定义在这里
  • total_size_before:Size of objects in heap set in constructor. 见这里
  • total_size_after:Size of objects in heap set in destructor. 见这里
  • holes_size_before:Total amount of space either wasted or contained in one of free lists before the current GC. 见这里
  • holes_size_after:Total amount of space either wasted or contained in one of free lists after the current GC. 见这里
  • allocated:自上次GC到这次GC之间分配的内存量,见这里
  • promoted:晋升的 Size of objects,见这里这里
  • semi_space_copied这里这里
  • nodes_died_in_new:源码中针对这里的Node说明很模糊,不清楚是不是内存页的意思,源码可以见这里这里
  • nodes_copied_in_new:同上
  • nodes_promoted:同上
  • promotion_ratio
  • average_survival_ratio:新生代对象平均生存率,计算见这里
  • promotion_rate
  • semi_space_copy_rate:新生代GC存活对象拷贝率,见这里
  • new_space_allocation_throughput:
  • context_disposal_rate:不太理解,计算见这里

4.2 Event::MINOR_MARK_COMPACTOR

  • pause
  • mutator
  • gc
  • reduce_memory
  • minor_mc
  • finish_sweeping
  • mark
  • mark.identify_global_handles
  • mark.seed
  • mark.roots
  • mark.weak
  • mark.global_handles
  • clear
  • clear.string_table
  • clear.weak_lists
  • evacuate
  • evacuate.copy
  • evacuate.update_pointers
  • evacuate.update_pointers.to_new
  • evacuate.update_pointers.to_new.tospace
  • evacuate.update_pointers.to_new.roots
  • evacuate.update_pointers.to_new.old
  • update_marking_deque
  • reset_liveness

5. 资料

EOF