Table of Contents

1. 前言

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

本文是Node内存相关文章的其中一篇,主要负责介绍内存监控相关的API及实践操作。

2. 内存Metrics API

本节介绍几个非常有用的获取内存相关信息的API方法。

2.1 process.memoryUsage

  • rss:node进程总内存占用量
  • heapTotal:总堆内存占用量(已申请下来的)
  • heapUsed:实际堆内存使用量
  • external:扩展等外部程序的内存占用量

常用来查看基础的内存信息,特别是rss很有用。

2.2 require(“v8”).getHeapStatistics

  • total_heap_size:总堆内存占用量(已申请下来的),同process.memoryUsage().heapTotal
  • total_heap_size_executable:字节码、优化后的代码等可执行的内容占用的内存量
  • total_physical_size:找到部分解释说是Commited size,测试下来该值
    • used_heap_size < total_physical_size < total_heap_size
    • Committed memory is, essentially, all the memory which has been allocated by applications, whether it’s used or not.
  • total_available_size:剩余可用的堆内存量,包括尚未向操作系统申请的部分,其实就是heap_size_limit - used_heap_size
  • used_heap_size:实际堆内存使用量,同process.memoryUsage().heapUsed
  • heap_size_limit:最大可用堆内存(上限)
  • malloced_memory:实际测试是一个很小的值,有解释说是:current amount of memory, obtained via malloc
  • peak_malloced_memory:没搜到任何说明,有必要可以读下node源码
  • does_zap_garbage:覆盖堆垃圾的模式的开关

常用来查看堆上限大小。

2.3 require(“v8”).getHeapSpaceStatistics

按内存空间分类space种类不同,给出不同空间的内存使用状况统计。实用性不大,一般来说实际使用中需要关心的其实只有old space,且仅仅只有large object space。

2.4 top & ps

使用系统ps命令更快获取进程的内存占用情况:

ps -p $(pgrep -lfa node | grep leak-and-gc.js | awk '{print $1}') -o rss,vsz

以及:

top -pid $(pgrep -lfa node | grep leak-and-gc.js | awk '{print $1}')

3. 内存泄露 & Chrome Dev Tool

本节会提供实际的内存泄露例子,并指导如何使用工具进行问题点的查找。

3.1 范例

内存泄露的实际例子可以使用下面5.1里的脚本进行试验。

3.2 查找问题

使用node的inspector来进行运行状态分析(当然,这工具可以做更多的事情)。关于Chrome Dev Tool,可以看官方教程

DEBUG=* node --inspect xxx.js

然后打开浏览器chrome://inspect/,找到你的脚本进行调试。也可以使用5.3里提到的工具,简化操作。

在打开的分析面板里,选中Memorytab,一共有3个选项可以操作:

  • Take heap snapshot
    • 获取node进程的堆快照
    • 点击之后需要等一段时间采集数据,然后就可以看到heap数据
    • 这个选项信息最全,一般是最常用的内存观察选项
    • 一般来说按最右边的Retained Size从大到小排序,就找到很有用的信息了
  • Record allocation profile
    • 以内存使用者的角度查看内存的分配情况
    • 在需要知道内存使用大户是哪个部分的业务的情况下很有用
    • 一样需要点击之后等一段时间进行采集
  • Record allocation timeline
    • 以时间轴为单位查看单位时间内的内存分配量
    • 在需要知道node的内存与时间关系的情况下很有用

结果页面上会有多个列,里面的意义这里简单介绍下,方便理解和查找问题:

  • Constructor:对象构造函数名称
  • Distance:对象到根节点的引用层级
  • Objects Count:对象的数量
  • Shallow Size: 对象本身所占用的内存,这里不包含其引用对象所占的内存
  • Retained Size: 对象所占总内存
  • Retainers:对象的引用层级关系

和RSS类似,这里的Retained Size是最重要需要关注的值。

在线上运行时有的时候如果需要看堆快照的话,可以使用第三方库bnoordhuis/node-heapdump在runtime使用代码导出快照。然后使用Chrome Dev Tool打开这个快照文件来查看内容。

Chrome Dev Tool可以加载多个堆快照,并对他们进行比对分析,这对内存量增长变化的分析非常有用。可以在程序里隔一定时间获取一次堆快照,然后线下慢慢分析。

更详细的可以看博客:

4. 核心内存Metrics

本节整理出监控Node内存的时候需要关心的核心Metrics。

4.1 Node内存

  • rss:node进程总内存占用量
  • heapTotal:总堆内存占用量(已申请下来的)
  • heapUsed:实际堆内存使用量
  • external:扩展等外部程序的内存占用量,在某些情况下rss很大但堆内存很小的时候,就需要定点关注外部插件使用的内存了
  • heapSizeLimit:堆内存上限,建议在node启动的时候每次都确定好堆内存大小

按空间分类的堆内存信息可以酌情收集,如果有需要分析单独的新生代老生代内存情况的话。

4.2 Node GC

所有的GC相关Metrics采集都应该按GC触发的时间节点进行收集,毕竟数据来自GC行为,没有GC行为那也就没数据可采集,所以不可能做到类似CPU和Node进程内存这样的按时间定时进行采集。

  • gcTime:GC发生的时间,精度可能需要提高到ms级别,而不是second级别
  • gcType:GC类型,一般来说新生代的scavenge回收可以忽略,这个类型GC的量级及可优化性都比较低
  • gcPause:GC中断时长,需要按不同GC类型进行分类收集,老生代的markSweepCompact数据最为关键
  • sizeBefore:GC前内存大小 bytes
  • sizeAfter:GC后内存大小 bytes
  • holesBefore:GC前内存空洞大小 bytes
  • holesAfter:GC后内存空洞大小 bytes
  • allocated:GC间,内存分配量 bytes
  • promoted:GC间,对象晋升量 bytes
  • allocationThroughput:GC间,新生代内存申请速率 bytes/ms
  • promotionRatio:当前GC中内存从新生代晋升到老生代的百分比 %
  • incrementalWalltime:增量标记时长 ms
  • compactionSpeed:内存Compacting速率 bytes/ms

5. 工具

5.1 内存泄露 or 正常运行 范例脚本

为了观察内存泄露和GC日志,需要一个范例运行的脚本,我这里制作了一个。如何使用请查看该脚本头部的注释:

5.2 GC解析管道脚本

可配合5.1的脚本一起使用,当然使用你自己的业务脚本也是OK的。

5.3 NIM(Node.js 调试管理工具)

可在node进程使用--inspect flag时,自动打开chrome的调试tab。

6. 资料

EOF