# 性能优化篇(四)
# NodeJS性能优化
- 内存回收
- 内存快照
- 压力测试
- 监控异常
# 内存泄漏的概念和造成的影响
# 内存泄漏
<!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
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
# 具体表现
# 压力测试寻找内存泄漏
# 测试工具
wrk
//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
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
node自带的
# 其他查找内存泄漏的工具
# 引起内存泄漏的原因及编码规范
# memeye
yarn add memeye -D
#或者
npm i memeye -D
1
2
3
2
3
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#访问接口
yarn test
1
2
2
红线会居高不下,一直增长,解决看下一小节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
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
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
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
# 闭包
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
2
3
4
5
6
7
8
9
10
11
12
# 频发的垃圾回收让GC没有机会工作
/**
*
*/
// 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
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
2
3
4
5
6
7
8
9
10
11
12
更多关于SharedArrayBuffer方面的知识请点击这里
# 小结
# 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
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
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
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
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