前端内存泄露问题

  最近发现项目有个bug,同时运行多个任务的时候,前端页面报内存不足而导致页面崩溃,这很明显就是内存泄露了。我查看了一下,运行的过程中,因为运行时间很久,所以前端和后台约定了,用计时器setInternal定时去请求后台运行状态,当运行状态为完成时,前端会清除定时器。我预估是因为计时器而导致的内存泄露,在执行计时器代码的时候,任务管理器的物理内存消耗一直在增加,这样的话,要是多个任务同时在执行,而且任务执行较久的话,那样物理内存就有可能会被占用完。后面我也复现了这个场景,果然是因为计时器的原因。

一、探究过程

疑惑1:会不会是因为JavaScript引擎是单线程,计时器会不断把事件不断放入事件队列,而任务执行时间很长,所以才会导致事件队列堆满而导致内存泄露?

资料答疑:首先,纠正大家一个错误的理解,定时器并不是严格意义上会按个多少秒执行的,它可能会出现执行延迟或提前。在运行本行代码的时候,将定时器的代码添加到了事件队列当中,而不是何时执行/运行代码。此时需要等到当前“事件处理程序”运行之后再去执行定时器代码。换句话说,就是,并非是设置的毫秒数后就执行定时器代码,执行的时间是有可能提前/延后的。同时,JavaScript引擎设置了:仅当队列中没有该定时器的任何其他代码实例时,才能够将定时器代码添加到队列。所以不会出现导致连续运行多次的情况。

疑惑2:那到底是什么导致内存泄露呢?

资料答疑:内联书写setInterval时,由于匿名函数被定义于全局中,不能够计时器的清除,因此很容易造成内存泄露。

二、实验测试

  刚好我的计时器setInternal是用匿名函数写的,很有可能是因为这个原因,所以我用了匿名函数和命名函数测试了一下。

1、匿名函数

代码:

 1 mounted(){
 2     let self = this;
 3     self.setInternalId = setInterval(()=>{
 4         let sum = 0,i=1;
 5         while(i<100000000){
 6             sum+=i++;
 7             // sum=parseInt(sum/2)
 8         }
 9         let now = new Date();
10         console.log(sum,'秒数:',now.getSeconds());
11     },2000);
12 },
13 beforeDestroy(){
14     let self = this
15     clearInterval(self.setInternalId);
16     console.log('消除定时器啦。。。。')
17 },

我发现物理内存是很缓慢增长的,所以要时间够长才能会有明显的区别,所以不能确定是匿名函数导致的。

 

2、命名函数

代码:

 1 mounted(){
 2     let self = this;
 3     self.setInternalId = setInterval(this.getSum,2000);
 4 },
 5 beforeDestroy(){
 6     let self = this
 7     clearInterval(self.setInternalId);
 8     console.log('消除定时器啦。。。。')
 9 },
10 methods:{
11     getSum(){
12         let sum = 0,i=1;
13         while(i<100000000){
14             sum+=i++;
15             // sum=parseInt(sum/2)
16         }
17         let now = new Date();
18         console.log(sum,'秒数:',now.getSeconds());
19     }
20 }

我持续观察了半个多小时物理内存的变化,发现内存是时增长时减低,增长或降低的幅度都不会很大。

 

3、增加http请求

后面我在里面增加一个htttp请求后台数据,发现物理内存是一直上升的,上升的幅度明显比匿名函数大,上升的速度也很快,所以可能是前端请求导致的内存泄露。同时为了校验是setInternal还是http请求导致的内存泄露,我做了以下的代码校验:

 1 mounted(){
 2     let self = this;
 3     self.setInternalId2  = setInterval(()=>{
 4         let now = new Date();
 5         console.log('第一个:',now.getSeconds());
 6         self.getAllTasks(1); // http请求
 7         self.setInternalId3 = setInterval(()=>{
 8             let sum =0,i=1;
 9             while(i<1000000){
10                 sum+=i++;
11             }
12             let now = new Date();
13             console.log('第二个:',now.getSeconds());
14         },3000)
15     },1000)
16 },
17 beforeDestroy(){
18     let self = this;
19     clearInterval(self.setInternalId2);
20     clearInterval(self.setInternalId3);
21     console.log('消除计时器。。。。。')
22 },

发中间的setInternal计时器删掉后,物理内存消耗还是持续快速的增长。后面查资料得知,浏览器对单窗口的http请求数量是有限制的,谷歌浏览器可以并发执行最多6个,所以会导致http请求阻塞,从而消耗内存

 

总结:这两个测试并不能证明是匿名函数所引起的内存泄露,很有可能是不断重复执行 ajax 引起的,但是我目前还查不到相关的资料证明,这个问题还有待考察。

 

三、常见的内存泄露

1、全局变量

  在浏览器的环境下,全局变量对象就是window,定义全局变量如下:

1 function index(){
2   bar = "dsasd"  ;
3 }
4 //上面代码相当于
5 function index(){
6   window.bar = "dsasd";   
7 }

如果定义变量的时候忘记加上let或var,这时一个全局变量就会被创建出来,还有另一种定义全局变量

1 function foo(){
2     this.variable= "potential accidental global";
3 }
4 // 函数自身发生了调用,this 指向全局对象(window),(这时候会为全局对象 window 添加一个 variable 属性)而不是 undefined。
5 
6 foo();

解决办法:为了防止这种错误的发生,在 JavaScript 文件开头添加 'use strict'; 语句。这个语句实际上开启了解释 JavaScript 代码的严格模式,这种模式可以避免创建意外的全局变量。

2、定时器和回调函数

  定时器造成内存泄露的主要原因是周期函数一直在运行,处理函数并不会被回收(只有周期函数停止运行之后才开始回收内存)。如果周期处理函数不能被回收,它的依赖程序也同样无法被回收。这意味着一些资源,也许是一些相当大的数据都也无法被回收。

  观察者即监听事件,当它们不再被需要的时候(或者关联对象将要失效的时候)显式地将他们移除是十分重要的。现在,当观察者对象失效的时候便会被回收,即便 listener 没有被明确地移除,绝大多数的浏览器可以或者将会支持这个特性。尽管如此,在对象被销毁之前移除观察者依然是一个好的实践。

3、DOM 之外的引用

4、闭包

 

内存泄露可以详细看:https://blog.csdn.net/fay462298322/article/details/53172176

posted on 2019-12-30 15:31  大黑ylx  阅读(1504)  评论(0编辑  收藏  举报