添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

版本信息

作者 时间 主要变更内容 链接
吴惟刚 2023年10月10日 http://www.wuweigang.com/?id=395

问题描述

页面滚动按需加载图片,越往下滚动页面图片越来越多(图片采用canvas渲染), 机器为8GB内存机器,在chrome下大约3000张图,浏览器崩溃, 每张图平均1MB

系统问题排查定位

如果不加载图片, 不渲染canvas, 浏览器内存不会增长, 基本问题就出现在这里

问题定位追踪测试

环境信息

测试如下

测试1

  1. 用js循环生成1000个canvas, 存储到某个变量中, canvas 宽高大小为512乘512,查阅知识一个,一个像素的canvas为4byte, 因此512乘512乘4=1MB

测试1结论

结论, 1000 canvas会导致内存升高1000 MB , 且不会销毁! window10 导致GPU 内存增高, window7导致页签内存增高。

测试2

  1. 用js循环生成100个 new Image(), 图片的大小为3.5MB (window7 500个)

在图片加载过程中,内存一度上升到3000MB 以上, 加载完,稳定后保持在 1773928 k

测试2结论

根据以上测试,猜测window10上的chrome做了改良,img 用完后会自动销毁,dom元素不会占用太多内存, window7 下的chrome无法销毁,除非该图片不被引用,否则内存永远存在!

测试3

  1. 用js循环生成1000个 canvas, 并且请求图片, 图片的大小为3.5MB, 将其渲染到canvas上 (window7 500个)

测试3结论

基于图片和canvas降低内存的优化建议

  1. 如果是使用 new Image() 加载图片,那么变量中不缓存 new Image() 对象

  2. 如果是使用 img加载图片,那么应该尽量使用虚拟列表,减少页面的img标签

  3. 如果使用canvas循环图片,不要在js变量中缓存canvas, 尽量使用虚拟dom,减少页面的canvas数量

  4. 图片加载过程中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
<!DOCTYPE html>
<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
<!DOCTYPE html>
<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
<!DOCTYPE html>
<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>