版本信息
作者 | 时间 | 主要变更内容 | 链接 |
---|---|---|---|
吴惟刚 | 2023年10月10日 | 无 | http://wuweigang.com/?id=395 |
问题描述
页面滚动按需加载图片,越往下滚动页面图片越来越多(图片采用canvas渲染), 机器为8GB内存机器,在chrome下大约3000张图,浏览器崩溃, 每张图平均1MB
系统问题排查定位
如果不加载图片, 不渲染canvas, 浏览器内存不会增长, 基本问题就出现在这里
问题定位追踪测试
环境信息
window10 chrome 117.0.5938.134 64
window7 chrome 75.0.3770.38 32
下面测试简称 window10和 window7
页签自己的进程为渲染进程, gpu进程各页签共享进程, chrome为多进程架构
测试如下
测试1
用js循环生成1000个canvas, 存储到某个变量中, canvas 宽高大小为512乘512,查阅知识一个,一个像素的canvas为4byte, 因此512乘512乘4=1MB
通过getImageData()可以获取canvas存的信息,像素信息使用 Uint8 来存储的,数组长度为 4, Uint8 占用内存为 1 个字节, 因此一共是 4 个字节,因此一个像素的canvas为4byte
window7
页签内存增高,内存占用 1065200 kbwindow10
致GPU 内存增高, GPU进程 1161568 k , 页签内存无变化,占用 44228k kb,
测试1结论
结论, 1000 canvas会导致内存升高1000 MB , 且不会销毁! window10 导致GPU 内存增高, window7导致页签内存增高。
测试2
用js循环生成100个 new Image(), 图片的大小为3.5MB (window7 500个)
window7
在图片加载过程中,内存一度上升到3000MB 以上, 加载完,稳定后保持在 1773928 k
将new Image() 全部存储到某个变量中, 内存不会下降销毁
将new Image() 加载完后,再将变量设置成null, 内存会下降销毁
将new Image() 加载完后,再将变量设置成null, 并且每个new Image()对象,创建一个 img标签,最后统一放到html中显示, 内存不会下载销毁
将new Image() 加载完后,再将变量设置成null, 并且仅仅创建一个 img标签,最后放到html中显示, 内存会下降销毁, 仅仅保持img中显示的那个内存
window10
在图片加载过程中,内存一度上升到3511932k 以上, 加载完,稳定后保持在 1773928 k将new Image() 全部存储到某个变量中, 内存会下降销毁, 内训会下降到 45684k
将new Image() 加载完后,再将变量设置成null, 内存会下降销毁
将new Image() 加载完后,再将变量设置成null, 并且每个new Image()对象,创建一个 img标签,最后统一放到html中显示, 内存会下载销毁
将new Image() 加载完后,再将变量设置成null, 并且仅仅创建一个 img标签,最后放到html中显示, 内存会下降销毁
测试2结论
根据以上测试,猜测window10上的chrome做了改良,img 用完后会自动销毁,dom元素不会占用太多内存, window7 下的chrome无法销毁,除非该图片不被引用,否则内存永远存在!
测试3
用js循环生成1000个 canvas, 并且请求图片, 图片的大小为3.5MB, 将其渲染到canvas上 (window7 500个)
window7
在图片加载过程中,内存一度上升到3000MB 以上, 加载完,稳定后保持在 1998728 k将canvas 全部存储到某个变量中, 内存不会下降销毁
将canvas 全部销毁,new Image() 不销毁, 内存不会下降销毁
将canvas 全部销毁,new Image() 全部销毁, 内存会下降销毁
window10
在图片加载过程中,内存一度上升到3000MB 以上, 加载完,稳定后保持在 1998728 k将canvas 全部存储到某个变量中, 内存不会下降销毁,保留canvas占用的内存。
渲染进程非激活情况下,稳定在1076260k, 激活情况下 39120k, GPU进程会优先接管canvas渲染进程 1342064k将canvas 全部销毁,new Image() 不销毁, 内存不会下降销毁,保留canvas占用的内存
将canvas 全部销毁,new Image() 全部销毁, 内存会下降销毁
测试3结论
window7下,canvas渲染图片内存会更高,且不会回收, 因此跟canvas和图片两者相关
window10, 各个渲染进程的canvas使用到的内存会在页签激活时,被gpu进程取代,内存转移到GPU内存,占用内存情况跟canvas相关,跟图片无关
基于图片和canvas降低内存的优化建议
如果是使用 new Image() 加载图片,那么变量中不缓存 new Image() 对象
如果是使用 img加载图片,那么应该尽量使用虚拟列表,减少页面的img标签
如果使用canvas循环图片,不要在js变量中缓存canvas, 尽量使用虚拟dom,减少页面的canvas数量
图片加载过程中window7会持续走高,在此过程并不会降低内存, 因此应该尽量避免持续请求图片,可以变成间隔请求
demo
大家可以自己复杂代码进行测试,图片自己在网上下载,把 src 修改为自己的图片即可
demo1
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
<title>canvas limit</title>
</head>
<body>
<div>
<span>内存单位: MB</span>
<input type="number" id="jsNumber"/>
</div>
<div>
<button id="jsCreate">创建</button>
</div>
<script>
// 放进该全局变量,防止GC
let queue = [];
let index = 0;
const documentFrame = document.createDocumentFragment();
// 创建 对象
const createObject = (count) => {
const size = 512;
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
context.fillRect(0, 0, size, size);
index++;
if (index === count) {
const span = document.createElement('span');
span.innerHTML = '完成' + count;
document.body.appendChild(span);
document.body.appendChild(documentFrame);
}
return canvas;
};
// 循环创建对象
const loopCreateObject = (n) => {
for (let i = 0; i < n; i++) {
queue.push(createObject(n));
}
};
const input = document.querySelector('#jsNumber');
const button = document.querySelector('#jsCreate');
button.addEventListener('click', (event) => {
event.preventDefault();
const number = input.value;
if (!Number.isNaN(Number(number))) {
queue = [];
loopCreateObject(Number(number));
console.log(`创建${number}MB canvas成功`);
}
});
</script>
</body>
</html>
demo2
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
<title>img limit</title>
</head>
<body>
<div>
<span>内存单位: MB</span>
<input type="number" id="jsNumber"/>
</div>
<div>
<button id="jsCreate">创建</button>
</div>
<script>
// 放进该全局变量,防止GC
let queue = [];
let index = 0;
const documentFrame = document.createDocumentFragment();
// 创建 对象
const createObject = (count) => {
index++;
let img = new Image();
img.index = index;
img.onload = () => {
if (img.index === count) {
const span = document.createElement('span');
span.innerHTML = '完成' + count;
document.body.appendChild(span);
document.body.appendChild(documentFrame);
}
// const targetImg = document.createElement('img');
// targetImg.src = 'img/img3024-4032.jpg?index=' + img.index;
// documentFrame.appendChild(targetImg);
// img = null;
};
// 加载图片出错的处理
img.onerror = (error) => {
};
// 84KB
// img.src = 'img/img512-min.jpg?index=' + index;
// 3.35MB
img.src = 'img/img3024-4032.jpg?index=' + index;
// img.src = 'img/img512-max.jpg?index=' + index;
// 240kb
// img.src = 'img/img512-zmax.jpg?index=' + index;
return img;
};
// 循环创建对象
const loopCreateObject = (n) => {
for (let i = 0; i < n; i++) {
queue.push(createObject(n));
}
};
const input = document.querySelector('#jsNumber');
const button = document.querySelector('#jsCreate');
button.addEventListener('click', (event) => {
event.preventDefault();
const number = input.value;
if (!Number.isNaN(Number(number))) {
queue = [];
loopCreateObject(Number(number));
console.log(`创建${number}MB canvas成功`);
}
});
</script>
</body>
</html>
demo3
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1"/>
<title>img-canvas limit</title>
</head>
<body>
<div>
<span>内存单位: MB</span>
<input type="number" id="jsNumber"/>
</div>
<div>
<button id="jsCreate">创建</button>
</div>
<script>
// 放进该全局变量,防止GC
let queue = [];
let index = 0;
const documentFrame = document.createDocumentFragment();
// 创建 对象
const createObject = (count) => {
const size = 512;
let canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
let context = canvas.getContext('2d');
index++;
let img = new Image();
img.index = index;
img.onload = () => {
context.drawImage(img, 0, 0, size, size);
if (img.index === count) {
const span = document.createElement('span');
span.innerHTML = '完成' + count;
document.body.appendChild(span);
document.body.appendChild(documentFrame);
}
/* const targetImg = document.createElement('img');
targetImg.src = 'img/img3024-4032.jpg?index=' + img.index;
documentFrame.appendChild(targetImg);
*/
context = null;
canvas = null;
img = null;
};
// 加载图片出错的处理
img.onerror = (error) => {
};
// 84KB
// img.src = 'img/img512-min.jpg?index=' + index;
// 3.35MB
img.src = 'img/img3024-4032.jpg?index=' + index;
// img.src = 'img/img512-max.jpg?index=' + index;
// 240kb
// img.src = 'img/img512-zmax.jpg?index=' + index;
return canvas;
};
// 循环创建对象
const loopCreateObject = (n) => {
for (let i = 0; i < n; i++) {
queue.push(createObject(n));
}
};
const input = document.querySelector('#jsNumber');
const button = document.querySelector('#jsCreate');
button.addEventListener('click', (event) => {
event.preventDefault();
const number = input.value;
if (!Number.isNaN(Number(number))) {
queue = [];
loopCreateObject(Number(number));
console.log(`创建${number}MB canvas成功`);
}
});
</script>
</body>
</html>
-
« 上一篇:
nvm基础使用(node版本管理工具)
-
desktopvoc
:下一篇 »
发表于 2023-11-09 下午 03:28:26
很好,很专业。