# 性能优化篇(四)

# NodeJS性能优化

  • 内存回收
  • 内存快照
  • 压力测试
  • 监控异常

# 内存泄漏的概念和造成的影响

# 内存泄漏

1595818508000_3A76AB3A-E101-47D1-8884-AB00F96ED751

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8" />
    <title>对象的探索</title>
  </head>

  <body>
    <script type="text/javascript">
      // 1.然后选择“Memory”标签,点击"take snapshot" 获取V8的堆内存快照。
      // 2:然后“command+f"(mac) 搜索“setName”,setName对象下面包含了shared
      // 3.raw_outer_scope_info_or_feedback_metadata,对闭包的引用数据就在这里面。
      function foo() {
        var myName = '东';
        var innerBar = {
          setName: function (newName) {
            myName = newName;
          },
        };
        return innerBar;
      }
      var bar = foo();
      bar.setName('东🏮');
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 具体表现

1595818610190_91152B84-F801-4515-A79F-40172BDEC929

6A19DECF-4882-4944-B7D6-00AC85CF7605

# 压力测试寻找内存泄漏

# 测试工具

wrk

1595818906499_6903C6A1-20AF-4D70-9AF8-9E274B614F37

wrkGITHUB地址

关于make

//package.json
{
  "name": "nodedemo",
  "version": "1.0.0",
  "description": "",
  "main": "demo1.js",
  "scripts": {//使用make命令生成wrk文件
    "test": "./wrk -t12 -c200 -d 60s http://127.0.0.1:3000"
  },//t线程  用了3个核,每个四个线程
  "keywords": [],
  "author": "",
  "license": "ISC"
}

//js
const http = require('http');

let s = '';
for (let i = 0; i < 1024 * 10; i++) {
  s += 'a';
}

const str = s;
const buffStr = Buffer.from(s);//实验结果提升20%左右

const server = http.createServer((req, res) => {
  if (req.url == '/buffer') {
    res.end(buffStr);
  } else if (req.url == '/string') {
    res.end(str);
  }
});
server.listen(3002);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

jMeter

1595818991477_24F29A53-C868-4E45-B5ED-EB4F4863E719

node自带的

1595819092976_B900B089-1EF5-4A1E-AFFB-A8C8E8FF73C0

# 其他查找内存泄漏的工具

1595819204112_B1DDA4E5-441C-4AB0-BE25-D858DAE1E629

1595819407314_4942539B-EE88-4931-B01C-75BBB2D60A70

# 引起内存泄漏的原因及编码规范

# memeye

yarn add memeye -D   
#或者
npm i  memeye -D
1
2
3

1595836787890_CD3A47C3-3AC6-4E33-B755-20D527207801

const http = require('http');
const memeye = require('memeye');
memeye();
let leakArray = [];
// leakArray = null;
const server = http.createServer((req, res) => {
  if (req.url == '/') {
    // const wm = new WeakMap();
    leakArray.push(Math.random());
    // wm.set(leakArray, leakArray);
    // console.log(wm.get(leakArray));
    console.log(leakArray);
    // leakArray = null;
    res.end('hello world');
  }
});
server.listen(3000);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#访问接口
yarn test
1
2

1595836939976_B693C12D-E542-48AA-97FF-5AC08969BBF5

红线会居高不下,一直增长,解决看下一小节node --expose-gc

# node --expose-gc

global.gc();
//返回当前Node.js使用情况
console.log('第一次', process.memoryUsage());

let map = new Map();
let key = new Array(5 * 1024 * 1024);
map.set(key, 1);
global.gc();
console.log('第二次', process.memoryUsage());

key = null;
console.log('第三次', process.memoryUsage());

map.delete(key);
key = null;
global.gc();
// console.log('第三次', process.memoryUsage());
console.log('第四次', process.memoryUsage());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
node --expose-gc demo3.js 
第一次 {
  rss: 19636224,
  heapTotal: 4644864,
  heapUsed: 1767296,
  external: 761096,
  arrayBuffers: 9382
}
第二次 {
  rss: 62472192,
  heapTotal: 49217536,
  heapUsed: 44176880,
  external: 918630,
  arrayBuffers: 9382
}
第三次 {
  rss: 62664704,
  heapTotal: 49217536,
  heapUsed: 44195880,
  external: 918630,
  arrayBuffers: 9382
}
第四次 {
  rss: 62787584,
  heapTotal: 11464704,
  heapUsed: 2234472,
  external: 918630,
  arrayBuffers: 9382
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

map有引用(强引用),即使置成null也不会立马执行gc

global.gc();
//返回当前Node.js使用情况
console.log('第一次', process.memoryUsage());

const wm = new WeakMap();
let key = new Array(5 * 1024 * 1024);
wm.set(key, 1);
key = null;
global.gc();
console.log('第二次', process.memoryUsage());

//输出
第一次 {
  rss: 19521536,
  heapTotal: 4644864,
  heapUsed: 1767288,
  external: 761096,
  arrayBuffers: 9382
}
第二次 {
  rss: 20455424,
  heapTotal: 7270400,
  heapUsed: 2233736,
  external: 918630,
  arrayBuffers: 9382
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

WeakMap对对象弱引用,可以不用删除key的值#

# 生成log

1595838032746_4A7548C3-0E25-49E1-ABB0-AD02142331EF

# 闭包

1595838362029_234D1E9E-59C6-43D8-9F88-FF671A0C5A13

function foo () {
  var tem_obj = {
    x: 1,
    y: 2,
    arr: new Array(20000)
  }
  //缓存x,用谁存谁,减少内存泄漏
  let closure = tem_obj.x
  return function () {
    console.log(closure)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 频发的垃圾回收让GC没有机会工作

1595838665534_577935AF-A4E7-4D08-9B6A-5306D4938E85

/**
 *
 */
// function strToArray(str) {
//   let i = 0;
//   const len = str.length;
//   let arr = Array(len);
//   for (; i < len; i++) {
//     arr[i] = str.charCodeAt(i) + Math.random();
//   }
//   return arr;
// }
// function foo() {
//   let i = 0;
//   let str = 'test v8 GC';
//   while (i++ < 10000) {
//     strToArray(str);
//   }
// }
// foo();

function strToArray(str, bufferView) {
  let i = 0;
  const len = str.length;
  for (; i < len; i++) {
    bufferView[i] = str.charCodeAt(i) + Math.random();
  }
  return bufferView;
}
function foo() {
  let i = 0;
  let str = 'test v8 GC';
  // SharedArrayBuffer = 连续的内存
  let bufferView = [];
  while (i++ < 10000) {
    strToArray(str, bufferView);
  }
}
foo();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
node --trace-gc  ...
#输出
#优化前
[55236:0x102d52000]       36 ms: Scavenge 2.4 (3.2) -> 2.0 (4.2) MB, 0.8 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[55236:0x102d52000]       46 ms: Scavenge 2.5 (4.2) -> 2.2 (5.2) MB, 0.9 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[55236:0x102d52000]       48 ms: Scavenge 3.1 (5.2) -> 2.1 (7.2) MB, 0.3 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[55236:0x102d52000]       50 ms: Scavenge 4.1 (7.2) -> 2.1 (7.2) MB, 0.1 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure
#优化后
[55227:0x102d52000]       37 ms: Scavenge 2.4 (3.2) -> 2.0 (4.2) MB, 0.8 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[55227:0x102d52000]       46 ms: Scavenge 2.5 (4.2) -> 2.2 (5.0) MB, 0.8 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failure 
[55227:0x102d52000]       50 ms: Scavenge 3.1 (5.0) -> 2.1 (7.0) MB, 0.3 / 0.0 ms  (average mu = 1.000, current mu = 1.000) allocation failur
# 老生代有机会参与
1
2
3
4
5
6
7
8
9
10
11
12

更多关于SharedArrayBuffer方面的知识请点击这里

# 小结

1595839631984_48F90C05-B208-422D-BCE8-59466F3CCBB5-1

# dom的内存泄漏

# dom

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>dom内存泄漏</title>
  </head>
  <body>
    <!--只有同时满足 DOM 树和 JavaScript 代码都不引用某个 DOM 节点,该节点才会被作为垃圾进行回收。 
        如果某个节点已从 DOM 树移除,但 JavaScript 仍然引用它,我们称此节点为“detached ”。
        因为 DOM 元素依然会呆在内存中。
        “detached ”节点是 DOM 内存泄漏的常见原因。-->
    <script>
      //万万记得避免全局变量 "use strict"
      //同时也要避免在函数内部不使用var的声明
      let detachedTree;
      function create() {
        var ul = document.createElement('ul');
        for (var i = 0; i < 100; i++) {
          var li = document.createElement('li');
          ul.appendChild(li);
        }
        detachedTree = ul;
      }
      create();
      //   detachedTree = null;
    </script>
    <script>
      //清除定时器⏲否则永远会保持对函数的引用
      function setCallback() {
        // 'unpacking' the data object
        let counter = 0;
        const hugeString = new Array(100000).join('x'); // gets removed when the setCallback returns
        return function cb() {
          counter++; // only counter is part of the callback's scope
          console.log(counter);
        };
      }
      const timerId = setInterval(setCallback(), 1000); // saving the interval ID
      clearInterval(timerId); // stopping the timer i.e. if button pressed
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

# 事件

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>前端内存泄漏粒子2</title>
  </head>
  <body>
    <script>
      // 事件监听存在于匿名函数 所以也无法移除
      const hugeString = new Array(100000).join('x');
      document.addEventListener('keyup', function () {
        // anonymous inline function - can't remove it
        doSomething(hugeString); // hugeString is now forever kept in the callback's scope
      });

      //终创建指向事件侦听器的引用并将其传递给 removeEventListener(),来注销不再需要的事件侦听器。
      function listener() {
        doSomething(hugeString);
      }
      document.addEventListener('keyup', listener); // named function can be referenced here...
      document.removeEventListener('keyup', listener); // ...and here

      //再或者创建只执行一次的监听函数
      document.addEventListener(
        'keyup',
        function listener() {
          doSomething(hugeString);
        },
        { once: true }
      ); // listener will be removed after running once
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# 游离的dom元素

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>游离的DOM元素2</title>
  </head>
  <body>
    <script>
      function createElement() {
        const div = document.createElement('div');
        div.id = 'detached';
        return div;
      }
      // this will keep referencing the DOM element even after deleteElement() is called
      const detachedDiv = createElement();
      document.body.appendChild(detachedDiv);
      function deleteElement() {
        document.body.removeChild(document.getElementById('detached'));
      }
      deleteElement(); // Heap snapshot will show detached div#detached
    </script>
    <!--解决办法是将 DOM 引用移入本地域。-->
    <script>
      function createElement() {
        // same as above
      }
      // DOM references are inside the function scope
      function appendElement() {
        const detachedDiv = createElement();
        document.body.appendChild(detachedDiv);
      }
      appendElement();
      function deleteElement() {
        document.body.removeChild(document.getElementById('detached'));
      }
      deleteElement(); // no detached div#detached elements in the Heap Snapshot
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

# 对象

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8" />
    <title>对象的探索</title>
  </head>

  <body>
    <script type="text/javascript">
      // 1.然后选择“Memory”标签,点击"take snapshot" 获取V8的堆内存快照。
      // 2:然后“command+f"(mac) 搜索“setName”,setName对象下面包含了shared
      // 3.raw_outer_scope_info_or_feedback_metadata,对闭包的引用数据就在这里面。
      function foo() {
        var myName = '东';
        var innerBar = {
          setName: function (newName) {
            myName = newName;
          },
        };
        return innerBar;
      }
      var bar = foo();
      bar.setName('东🏮');
    </script>
  </body>
</html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# 调试神器

# 同构化的原理

spa

vue.js -> vue.router.js -> main.js -> app.js -> vue 组件 ->

数据 -...js -> 交互

切页的方便 a/b -> c/d (变化的部分内容)

mpa

可见可操作 index.html 直接 a 元素 直接输出

bigpipe main.div -> chunk -> chunk....

--> 加载 js 代理 a 元素

后端 c/d 直接刷新来的 点过来的。一片段 整页

https://www.npmjs.com/package/tti-polyfill

https://www.npmjs.com/package/web-vitals