JavaScript中的内存泄露

一. 定义

js如同其他高级语言一样都有垃圾回收机制,会周期性的检查之前分配的内存是否可达,帮助开发者管理内存。对不可达的内存通过算法确定、标记并适时回收。

而内存泄露则可以理解为当应用程序不再需要占用内存时,由于某些原因操作系统未回收其内存。

二. 堆?栈?队列?

2.1 任务队列

由于js是单线程,所以所有的任务都需要排队。学过操作系统的都知道单线程最大的浪费就是输入输出时的等待,所以JavaScript设计者采取了另一种策略:主线程忽略IO设备,挂起处于等待中的任务,先运行它后面的任务。等到IO设备返回结果后再讲挂起任务继续执行下去

2.1.1 同步任务(synchronous)

在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务

2.1.2 异步任务(asynchronous)

不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

2.2 函数执行栈

在这里插入图片描述

函数的嵌套调用就是通过函数执行栈。每嵌套一层向栈中推入函数信息,得到返回值后出栈

2.3 堆

而主要的用户创建的对象就存放在堆中。内存泄漏定位的主要区域就在这里

三. Mark-and-Sweep(标记扫描)算法

大部分垃圾回收机制的算法均称为mark-and-sweep,由以下几部分组成

  • 垃圾回收器创建了一个“roots”列表。Roots通常是代码中全局变量的引用。JavaScript 中,“window”对象是一个全局变量,被当作rootwindow对象总是存在,因此垃圾回收器可以检查它和它的所有子对象是否存在(即不是垃圾);
  • 所有的 roots 被检查和标记为激活(即不是垃圾)。所有的子对象也被递归地检查。从root开始的所有对象如果是可达的,它就不被当作垃圾。
  • 所有未被标记的内存会被当做垃圾,收集器现在可以释放内存,归还给操作系统了。

在这里插入图片描述

不需要的引用是指开发者明知内存引用不再需要,却由于某些原因,它仍被留在激活的root树中。在JavaScript中,不需要的引用是保留在代码中的变量,它不再需要,却指向一块本该被释放的内存。

四. 举几个栗子

4.1 意外的全局变量
4.1.1 粗心

如果定义时忘记了var那么引擎会自动帮你创建一个全局变量。虽然无伤大雅还是尽量避免的好

4.1.2 this创建的意外
function foo() { 
    this.a = "accidental global"; 
}
4.2 闭包&&定时器
var theThing = null; 
var replaceThing = function () { 
  var originalThing = theThing; 
  var unused = function () { 
    if (originalThing) {
      console.log("hi"); 
    }
  }; 
 
  theThing = { 
    longStr: new Array(1000000).join('*'), 
    someMethod: function () { 
      console.log(someMessage); 
    } 
  }; 
}; 
 
setInterval(replaceThing, 1000);

代码片段做了一件事情:每次调用replaceThingtheThing得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量unused是一个引用originalThing的闭包(先前的replaceThing又调用了theThing)。思绪混乱了吗?

一旦一个作用域被创建为闭包,那么它的父作用域将被共享

最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod()可以通过theThing使用,someMethod()unused分享闭包作用域,尽管unused从未使用。它引用的originalThing迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄露。

当然,我们可以通过在replaceThing的最后添加originalThing = null来修复此问题。

4.3 DOM引用

当需要删除DOM的引用时候注意一点:同一份DOM一般拥有两份引用:DOM树和字典

var elements = {
    button: document.getElementById('button'),
    image: document.getElementById('image')
};
function doStuff() {
    elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
    // The image is a direct child of the body element.
    document.body.removeChild(document.getElementById('image'));
    // At this point, we still have a reference to #button in the
    //global elements object. In other words, the button element is
    //still in memory and cannot be collected by the GC.
    elements.img = null		// tell the engine to collect the memory
}

还有一个额外的考虑,当涉及 DOM 树内部或叶子节点的引用时,必须考虑这一点。假设你在 JavaScript 代码中保留了对 table 特定单元格(<td>)的引用。有一天,你决定从 DOM 中删除该 table,但扔保留着对该单元格的引用。直观地来看,可以假设 GC 将收集除了该单元格之外所有的内容。实际上,这不会发生的:该单元格是该 table 的子节点,并且 children 保持着对它们 parents 的引用。也就是说,在 JavaScript 代码中对单元格的引用会导致整个表都保留在内存中的。保留 DOM 元素的引用时,需要仔细考虑。

好啦今天的话题就讲到这里
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值