阿里云 黄一君《唯快不破&高效定位线上+Node-js+应用内存泄漏》

很多前端工程师在做页面性能调优的过程中,极少关注代码本身的执行效率,更多关注的是网络消耗,比如资源合并减少请求数、压缩降低资源大小、缓存等. 我并不觉得这不合理,相反,在很大程度上这是足够正确的做法,举个例子, JS本身的执行时间是30ms(毫秒),在动辄三五秒的页面加载时间中的占比实在太低了,就算拼了命把性能提升10倍,执行时间降到3ms,整体性能提升也微不足道,甚至在用户层面都无法感知. 因此去优化其它性能消耗的大头更加明智.
展开查看详情

1.唯快不不破 ⾼高效定位线上 Node.js 应⽤用内存泄漏漏

2.关于我 ❖ @hyj1991 (GitHub, CNode) ❖ @⻩黄⼀一君,Easy-Monitor 作者 ❖ @阿⾥里里云计算有限公司,⾼高级开发⼯工程师,Node.js 性能平台

3.背景 ❖作为中间层,前后端分离 ❖⻓长连接,纯服务端应⽤用 ❖NW.js、Electron 等构建跨平台客户端 Java Services RPC calls, Node.js Applications protocols CDN, Tengine(Nginx)… ❖ Building new products Distributed Systems ❖ Refactoring old products(PHP/Java Web) (C++, Erlang, Java….) ❖ Rapid iteration, better communication

4.探究 V8 GC 过程

5.堆内内存划分 Code Space v8 编译后的可执⾏行行代码 Map Space Old Space Object 指向的 在 new space 中经过两次 隐藏类元对象 GC 依旧存活的对象晋升 到 old space 中 Large Object Space New Space ⼤大对象 (⼤大于 507136 byte) 对半分割为两个部分, 同⼀一时刻只使⽤用其中的 ⼀一半,绝⼤大部分对象的 创建和销毁都在这⾥里里发⽣生

6.新⽣生代(Scavenge 算法) a1 D A a2 a2-1 e1 E Root B b1 e2 C c1 c1-1 c1-1- c1-2

7.新⽣生代(Scavenge 算法) Allocation Pointer to space c1- A B C a1 a2 b1 a2- c1- D c1- E e1 e2 c1 1- 1 1 2 1 from space not in use

8.新⽣生代(Flip) from space c1- A B C a1 a2 b1 a2- c1- D c1- E e1 e2 c1 1- 1 1 2 1 to space Scan Allocation Pointer Pointer

9.新⽣生代(Copy Roots) from space c1- A B C a1 a2 b1 a2- c1- D c1- E e1 e2 c1 1- 1 1 2 1 to space A B C Scan Allocation Pointer Pointer

10.新⽣生代(BFS) from space c1- A B C a1 a2 b1 a2- c1- D c1- E e1 e2 c1 1- 1 1 2 1 to space A B C a1 a2 b1 c1 Scan Allocation Pointer Pointer

11.新⽣生代(Scan 指针和 Allocation 指针重合) from space c1- A B C a1 a2 b1 a2- c1- D c1- E e1 e2 c1 1- 1 1 2 1 to space c1- A B C a1 a2 b1 a2- c1- c1- c1 1- 1 1 2 1 Scan Allocation Pointer Pointer

12.⽼老老⽣生代(Mark-Sweep/Mark-Compact 算法) A D H E Root B F G C J I K marking deque

13.⽼老老⽣生代(Mark-Sweep/Mark-Compact 算法) A D H E Root B C F G B C J A I K marking deque

14.⽼老老⽣生代(Mark-Sweep/Mark-Compact 算法) A D H E Root B F G B C J A I K marking deque

15.⽼老老⽣生代(Mark-Sweep/Mark-Compact 算法) A D H E Root B F F G E C J A I K marking deque

16.⽼老老⽣生代(Mark-Sweep/Mark-Compact 算法) A D H E Root B G F G E C J A I K marking deque

17.⽼老老⽣生代(Mark-Sweep/Mark-Compact 算法) A D H E Root B F G C J I K marking deque

18.⽼老老⽣生代(overflow) A D H E Root B overflow E F G A C J marking deque I K 先将 G 标记为灰⾊色,但是不不放⼊入 marking queue,那么从 E 开始 pop,很快 marking queue 就会被清空;此时再遍历整个堆,找到灰⾊色的对象放⼊入 marking queue,继续原样 标记执⾏行行。

19.增量量式标记 ❖每次 Mark-Sweep 需要全量量扫描整个堆,开销过⼤大 ❖堆达到⼀一定⼤大⼩小时,执⾏行行增量量标记 (incremental_marking)

20.探究 Heapsnapshot

21.什什么是 Heapsnapshot ❖ Root 到应⽤用运⾏行行⽣生成的各个对象间的引⽤用关系

22.获取 Heapsnapshot (heapdump) ❖ 使⽤用 writeSnapshot 按需 获取堆快照 ❖ 使⽤用 kill -USR2 <pid> 按需 获取堆快照

23.获取 Heapsnapshot (v8- profiler) ❖ 传⼊入回调获取完整序列列化 堆快照 ❖ 不不传回调返回 transform 流 式获取堆快照

24.获取 Heapsnapshot (Node.js 性能平 台)

25.Heapsnapshot 数据结构详解 snapshot { snapshot: {} node1 describe node & edge edge1 nodes: [] node2 node & edge’s name edges: [] edge2 strings: [] } node3

26.Heapsnapshot 数据结构详解 ( snapshot ) ❖ meta.node_fields: ⻓长度为⼀一个 node 实际⻓长度,每⼀一个元素代表其含 ❖ meta.node_types: 每⼀一个 node 中每⼀一位的类型,第⼀一位 type 是⼀一个数 ❖ meta.edge_fields: ⻓长度为⼀一个 edge 实际⻓长度,每⼀一个元素代表其含义 ❖ meta.edge_types: 每⼀一个 edge 中每⼀一位的类型,第⼀一位 type 是⼀一个数

27.Heapsnapshot 数据结构详解 ( node 和 edge 的对应关系 ) node1 node2 node3 edge_count: edge_count: edge_count: 2 1 3 edge1 edge2 edge3 edge4 edge5 edge6

28.Heapsnapshot 数据结构详解 ( node 和 node 的引⽤用关系 ) edge node1 to_node: node2

29.定位泄漏漏点(内存图) 1 2 3 4 5 6 7 8