Js中常见的内存泄漏场景

常见的内存泄漏场景

内存泄漏Memory Leak是指程序中已动态分配的堆内存由于疏忽或错误等原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。对于内存泄露的检测,Chrome提供了性能分析工具Performance,可以比较方便的查看内存的占用情况等。

内存回收机制

像C语言这样的底层语言一般都有底层的内存管理接口,例如malloc()和free()等,对于JavaScript而言在创建变量时其会自动进行分配内存,并且在不使用它们时自动释放。在Js七种基本类型中的引用类型Object的变量其占据内存空间大且大小不固定,在堆内存中实际存储对象,在栈内存中存储对象的指针,对于对象的访问是按引用访问的。在栈区中执行的变量等是通过值访问,当其作用域销毁后变量也就随之销毁,而使用引用访问的堆区变量,在一个作用域消失后还可能在外层作用域或者其他作用域仍然存在引用,不能直接销毁,此时就需要通过算法计算该堆区变量是否属于不再需要的变量,从而决定是否需要进行内存回收,在Js中主要有引用计数与标记清除两种垃圾回收算法。

引用计数算法

对于引用计数垃圾回收算法,把对象是否不再需要简化定义为该对象有没有其他变量或对象引用到它,如果没有引用指向该对象,该对象将被垃圾回收机制回收。在这里,对象的概念不仅特指JavaScript对象,还包括函数作用域或者全局词法作用域。引用计数垃圾回收算法使用比较少,主要是在IE6与IE7等低版本IE浏览器中使用。

var obj = { a : { b: 11 } } // 此时两个对象被创建,一个作为另一个的a属性被引用称为对象1,另一个被obj变量引用称为对象2 // 此时两个对象都有被引用的变量,都不能回收内存 var obj2 = obj; // 此时对于obj所引用的对象2,已经有obj与Obj2两个变量的引用 obj = null; // 将obj对于对象2的引用解除,此时对象2还存在obj2一个引用 var a2 = obj2.a; // 引用对象1,此时对象1有a与a2两个引用 obj2 = null; // 解除对象2的一个引用,此时对象2的引用数量为0,可以被垃圾回收 // 对象2的a属性引用被解除,此时对象1只有a2一个引用 a2 = null; // 解除a2对于对象1的引用,此时对象1可以被垃圾回收

但是对于引用计数垃圾回收算法有个限制,当对象循环引用时,就会造成内存泄漏,也就是引用计数垃圾回收算法无法处理循环引用的对象。

function funct() { var obj = {}; // 命名为对象1,此时引用数量为1 var obj2 = {}; // 命名为对象2,此时引用数量为1 obj.a = obj2; // obj的a属性引用obj2,此时对象2的引用数量为2 obj2.a = obj; // obj2的a属性引用obj,此时对象1的引用数量为2 return 1; // 此时执行栈的obj变量与obj2变量被销毁,对象1与对象2的引用数量减1 // 对象1的引用数量为1,对象2的引用数量为1,两个对象都不会被引用计数算法垃圾回收 } funct(); // 两个对象被创建,并互相引用,形成了一个循环,它们被调用之后会离开函数作用域,所以它们已经不再需要了,可以被回收了,然而引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。 标记清除算法

对于引用计数垃圾回收算法,把对象是否不再需要简化定义为该对象是否可以获得,该算法设置一个叫做根root的对象,在Javascript里根是全局对象,垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象,以此不断向下查找。从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象,这样便解决了循环引用的问题。所有现代浏览器都使用了标记清除垃圾回收算法,所有对JavaScript垃圾回收算法的改进都是基于标记清除算法的改进。

垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记。

然后,它会去掉运行环境中的变量以及被环境中变量所引用的变量的标记。

此后,依然有标记的变量就被视为准备删除的变量,原因是在运行环境中已经无法访问到这些变量了。

最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

常见内存泄漏场景 意外的全局变量

在JavaScript中并未严格定义对未声明变量的处理方式,即使在局部函数作用域中依旧能够定义全局变量,这种意外的全局变量可能会存储大量数据,且由于其是能够通过全局对象例如window能够访问到的,所以进行内存回收时不认为其是需要回收的内存而一直存在,只有在窗口关闭或者刷新页面时才能够被释放,造成意外的内存泄漏,在JavaScript的严格模式下此种意外的全局变量定义方式会抛出异常,另外同样可以使用eslint进行此种状态的预检查。事实上定义全局变量并不是一个好习惯,如果必须使用全局变量存储大量数据时,确保用完以后把它设置为null或者重新定义,与全局变量相关的增加内存消耗的一个主因是缓存,缓存数据是为了重用,缓存必须有一个大小上限才有用,高内存消耗导致缓存突破上限,因为缓存内容无法被回收。

function funct(){ name = "name"; } funct(); console.log(window.name); // name delete window.name; // 不手动删除则在不关闭或刷新窗口的情况下一直存在 被遗忘的计时器

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wspxwx.html