唯快不不破 高效定位线上 Node.js 应 用内存泄漏漏
关于我 @hyj1991 (GitHub, CNode) @ 黄 一君,Easy-Monitor 作者 @ 阿 里里云计算有限公司, 高级开发 工程师,Node.js 性能平台
背景 作为中间层, 前后端分离 长连接, 纯服务端应 用 NW.js Electron 等构建跨平台客户端 Java Services RPC calls, protocols Node.js Applications CDN, Tengine(Nginx) Distributed Systems (C++, Erlang, Java.) Building new products Refactoring old products(php/java Web) Rapid iteration, better communication
探究 V8 GC 过程
堆内内存划分 Map Space Object 指向的隐藏类元对象 Large Object Space 大对象 ( 大于 507136 byte) Code Space v8 编译后的可执 行行代码 Old Space 在 new space 中经过两次 GC 依旧存活的对象晋升到 old space 中 New Space 对半分割为两个部分, 同 一时刻只使 用其中的 一半, 绝 大部分对象的创建和销毁都在这 里里发 生
新 生代 (Scavenge 算法 ) a1 D A a2 a2-1 e1 Root B b1 E e2 C c1 c1-1 c1-1- c1-2
新 生代 (Scavenge 算法 ) to space Allocation Pointer A B C a1 a2 b1 a2-1 c1 c1-1 c1- D c1- E e1 e2 1-2 1 from space not in use
新 生代 (Flip) from space A B C a1 a2 b1 a2-1 c1 c1-1 c1- D c1- E e1 e2 1-2 1 to space Scan Pointer Allocation Pointer
新 生代 (Copy Roots) from space A B C a1 a2 b1 a2-1 c1 c1-1 c1- D c1- E e1 e2 1-2 1 to space A B C Scan Pointer Allocation Pointer
新 生代 (BFS) from space A B C a1 a2 b1 a2-1 c1 c1-1 c1- D c1- E e1 e2 1-2 1 to space A B C a1 a2 b1 c1 Scan Pointer Allocation Pointer
新 生代 (Scan 指针和 Allocation 指针重合 ) from space A B C a1 a2 b1 a2-1 c1 c1-1 c1- D c1- E e1 e2 1-2 1 to space A B C a1 a2 b1 c1 a2-1 c1-1 c1-2 c1-1- 1 Scan Pointer Allocation Pointer
老老 生代 (Mark-Sweep/Mark-Compact 算法 ) A D H Root B E F G C I J K marking deque
老老 生代 (Mark-Sweep/Mark-Compact 算法 ) A D H E Root B F G C B C J A I K marking deque
老老 生代 (Mark-Sweep/Mark-Compact 算法 ) A D H Root B E C I F G J K B A marking deque
老老 生代 (Mark-Sweep/Mark-Compact 算法 ) A D H Root B E F G F E C I J K A marking deque
老老 生代 (Mark-Sweep/Mark-Compact 算法 ) A D H Root B E F G G E C I J K A marking deque
老老 生代 (Mark-Sweep/Mark-Compact 算法 ) A D H Root B E F G C I J K marking deque
老老 生代 (overflow) A D H E Root B overflow E F G A C I J marking deque K 先将 G 标记为灰 色, 但是不不放 入 marking queue, 那么从 E 开始 pop, 很快 marking queue 就会被清空 ; 此时再遍历整个堆, 找到灰 色的对象放 入 marking queue, 继续原样 标记执 行行
增量量式标记 每次 Mark-Sweep 需要全量量扫描整个堆, 开销过 大 堆达到 一定 大 小时, 执 行行增量量标记 (incremental_marking)
探究 Heapsnapshot
什什么是 Heapsnapshot Root 到应 用运 行行 生成的各个对象间的引 用关系
获取 Heapsnapshot (heapdump) 使 用 writesnapshot 按需获取堆快照 使 用 kill -USR2 <pid> 按需获取堆快照
获取 Heapsnapshot (v8- profiler) 传 入回调获取完整序列列化堆快照 不不传回调返回 transform 流式获取堆快照
获取 Heapsnapshot (Node.js 性能平台 )
Heapsnapshot 数据结构详解 { snapshot: {} nodes: [] node1 edge1 snapshot describe node & edge edges: [] node2 node & edge s name strings: [] } edge2 node3
Heapsnapshot 数据结构详解 ( snapshot ) meta.node_fields: 长度为 一个 node 实际 长度, 每 一个元素代表其含 meta.node_types: 每 一个 node 中每 一位的类型, 第 一位 type 是 一个数 meta.edge_fields: 长度为 一个 edge 实际 长度, 每 一个元素代表其含义 meta.edge_types: 每 一个 edge 中每 一位的类型, 第 一位 type 是 一个数
Heapsnapshot 数据结构详解 ( node 和 edge 的对应关系 ) node1 node2 node3 edge_count: 2 edge_count: 1 edge_count: 3 edge1 edge2 edge3 edge4 edge5 edge6
Heapsnapshot 数据结构详解 ( node 和 node 的引 用关系 ) edge node1 to_node: node2
定位泄漏漏点 ( 内存图 ) 1 2 3 4 5 6 7 8
定位泄漏漏点 ( 支配树 ) 1 2 3 5 4 7 6 8
定位泄漏漏点 (GC roots)
经典案例例实战
EventHandle if (!client) { client = Client.create({ refreshinterval: 30000, requesttimeout: 5000, urllib: urllib }); } client.on('error', err => { // error 处理理... });
EventHandle
EventHandle MyEvent extends events.eventemitter myevent.on( some, listener ) myevent._events[ some ] = [listener1, listener2, listener3, ]
EventHandle home.js()@3454 context Client@4607 EventHandlers Array listener1, _events erro array
EventHandle 总结 设置 max listeners ( setmaxlisteners ) 调 用 on 方法时记得留留意下是否会重复执 行行
动态更更新模块 const a = require('a'); const b = require('b'); const c = require('c'); // 业务处理理... delete require.cache[require.resolve('a')]; delete require.cache[require.resolve('b')]; delete require.cache[require.resolve('c')];
动态更更新模块 delete require.cache[ mod1-v1 ] require( mod1-v2 ) mod1- mod2- mod1- mod1- node 进程
动态更更新模块
动态更更新模块 Mod A add reference to Mod A.children B 缓存是否存在 require(./b ) not exists 解析 B 模 add cache
动态更更新模块 总结 清除模块的缓存需要清除 require.cache 和所有 父引 用 清除的模块中包含定时器器 socket 连接等资源的需要 手动释放 不不建议在线上对 Node.js 进程做模块的热加载
网红 Vue beforecreate created beforemount mounted
网红 Vue const app = new Vue({ created() { setinterval(function () { // 业务处理理.. }); }, template: `<xxx></xxx>` }); render.rendertostring(app, (err, html) => { // 业务处理理.. });
网红 Vue
网红 Vue created 会在 ssr 时执 行行, 这 里里创建的 timer 等资源属性的元素会在服务端累加
网红 Vue 总结 加强 SSR 生命周期的理理解 SSR 中处于服务端执 行行的 生命周期钩 子中不不添加资源属性元素
总结与思考
线上诊断两 大难题 Out of Memory 进程仍存活但 无故不不响应