(头等人,有本事,没脾气;二等人,有本事,有脾气;末等人,没本事,大脾气。——南怀瑾)
NodeJs内存分析的必要性
回顾过去,我们排查web应用问题的途径通常是一下几种
- 有显式异常的情况下(比如前端报错或者后端接口异常等),可以直接定位前端模块,确认不是前端的问题,则再排查对应的后端接口并根据异常的提示直接查找对应代码。这种问题的定位和排查速度最快最简单。该类问题通常是发版前的代码测试不完全导致,也是日常线上问题的主要排查途径
- 没有显式异常,用户前端部分页面出现性能问题,比如加载时间长,后端接口请求时间长等。通常这类问题是在产品某个版本发布后造成,确认不是前端的问题,则将部分超时的接口发给后端,后端先可以快速定位到这几个接口,排查代码问题。该类问题通常在测试阶段很难发现,也是常见的线上类问题
- 没有显式异常,用户也没有反馈问题,但后端服务得内存持续上升,无法定位到具体的接口,查看最近发版的代码也没有问题,那么该问题就是典型的内存泄漏,需要借助专业的工具和可视化来解决
针对第三种情况,我们无法快速的定位到问题代码,但可以借助排查工具,head-dump和chrom的memory等一起分析内存使用情况,排查内存空间占用大的对象,再利用该对象定位业务代码进行排查
head-dump
模拟数据泄漏代码
模拟一个后端服务内存泄漏场景,初始化全局变量customGlobalList,每次访问接口都向该数组添加100w个数字,随着接口访问频次的增加,该数组的容量也随之增加,从而造成内存泄漏的问题
import { Injectable } from "@nestjs/common";
const customGlobalList = [];
@Injectable()
export class AppService {
constructor(
) { }
getNestCli () {
for (let index = 0; index < 1000000; index++) {
customGlobalList.push(index);
}
}
}
添加内存快照打印代码
通过接口或者定时器触发,为了简单起见,我在这里通过接口触发
import { Injectable } from "@nestjs/common";
import * as heapdump from "heapdump";
const customGlobalList = [];
@Injectable()
export class AppService {
constructor(
) { }
getNestCli () {
for (let index = 0; index < 1000000; index++) {
customGlobalList.push(index);
}
}
getHead () {
console.log('fileName', `${__dirname}/${new Date().valueOf()}.heapsnapshot`);
const fileName = `${__dirname}/${new Date().valueOf()}.heapsnapshot`;
heapdump.writeSnapshot(fileName);
}
}
通过chrom memory进行分析
我们分别打印两次快照,一次是服务刚启动时打印,一次是访问多次内存泄漏接口时打印
加载内存快照
打开chrom控制台,选择memory。左侧是已经加载好的内存快照,其中192mb的是有内存泄漏问题的,13.4m的是服务刚启动时的快照。
快照分析类型
Summary
按照对象列表排列,可以根据排序快速定位占用内存大的对象。
Shallow Size
对象自身占用的内存大小
Retained Size
对象被回收后可释放的内存大小
通过队Retained Size排序,我们发现array数组中的对象占据了94%的内存空间,
我们展开数组列表,查看下方的对象信息,就能够找到customGlobalList了
Comparison
通过对比两个快照来快速获悉以下信息
- New - Comparison 特有 - 新增项
- Deleted - Comparison 特有 - 删除项
- Delta - Comparison 特有 - 增量
- Alloc. Size - Comparison 特有 - 内存分配大小
- Freed Size - Comparison 特有 - 释放大小
- Size Delta - Comparison 特有 - 内存增量
Containment
查看内存内容。更适合查看对象结构,有助于分析对象的引用情况。适用于分析闭包以及深入分析对象
Statistics
统计视图,快速获悉各数据结构的内存占比