JavaScript中的垃圾回收机制

本文详细解析了垃圾回收(GC)的基本概念,包括其在不同编程语言中的应用,如Java、JavaScript和Python。探讨了GC的重要性,即减轻开发者管理内存的负担,减少BUG,提高编程效率。文章还介绍了堆内存、内存泄漏的概念,以及如何通过标记-清除法进行垃圾回收,同时讨论了引用计数法的局限性和循环引用问题。最后,提供了避免内存泄漏的方法,帮助开发者提升代码质量。

什么是GC:GC是Garbage Collection的缩写,意为垃圾回收;不是所有语言的世界里面都有GC,相对来说,高级语言里面一般会带GC,比如Java,JavaScriptPython,在没有GC的世界里,需要程序员手动管理内存,比如C语言我们常见的malloc/free,其实就是memory allocation的缩写。当然,还有C++里面的new/delete。垃圾收集器会按照固定的时间间隔,周期性的找出不再继续使用的变量,然后释放其占用的内存什么叫不再继续使用的变量?不再使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。全局变量的生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收

为什么要使用GC:省去开发者手动管理内存的麻烦(不是每个开发者都能管理好),从而减少BUG的产生,把精力留给更本质的编程工作。

 为什么要学点GC:一句话:“为了更好地找BUG”。其实是比较欠缺计算机体系知识的(以我自己举例),比如操作系统,计算机组成原理和计算机网络。但是这些知识,确实解决实际开发问题的根基所在,所以,有一个更好的基础能带来更快的开发/维护效率,而学习GC能为我们提供部分计算机体系知识的思想。比如"标记-清除法"就和操作系统页面置换第二次机会算法类似,所以知识是融汇贯通的,这里我们接触了,那么以后需要学习更多知识的时候就会更得心应手。

堆:动态存放对象的内存空间 ;

内存泄漏:由于疏忽或错误造成程序未能释放那些已经不再使用的内存,造成内存的浪费。

关于内存泄漏:

  1. 即使是1byte的内存,也叫内存泄漏,并不一定是导致浏览器崩溃、卡顿才能叫做内存泄漏。
  2. 一般是堆区内存泄漏,栈区不会泄漏。
  3. 基本类型的值存在内存中,被保存在栈内存中,引用类型的值是对象,保存在堆内存中。所以对象、数组之类的,才会发生内存泄漏
  4. 使用chorme监控内存泄漏

垃圾回收策略-标记清除法:当前采用的垃圾收集策略

工作原理:当变量进入环境时(例如在函数中声明一个变量),将这个变量标记为“进入环境”,当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。

工作流程:

  1. 垃圾收集器会在运行的时候会给存储在内存中的所有变量都加上标记
  2. 去掉环境中的变量以及被环境中的变量引用的变量的标记。
  3. 那些还存在标记的变量被视为准备删除的变量
  4. 最后垃圾收集器会执行最后一步内存清除的工作,销毁那些带标记的值并回收它们所占用的内存空间

 

垃圾回收策略-引用计数法(被废弃的垃圾收集策略):顾名思义,让所有对象实现记录下有多少“程序”在引用自己,跟踪记录每个值被引用的次数;在老版本的浏览器中(对,又是IE),IE9以下BOM和DOM对象就是使用C++以COM对象的形式实现的。COM的垃圾收集机制采用的就是引用计数策略,这种机制在出现循环引用的时候永远都释放不掉内存。

var element = document.getElementById('something');

var myObject = new Object();

myObject.element = element; // element属性指向dom

element.someThing = myObject; // someThing回指myObject 出现循环引用(两个对象一直互相包含 一直存在计数)。

解决方式是,当我们不使用它们的时候,手动切断链接:myObject.element = null; element.someThing = null;

 

哪些情况会引起内存泄漏:

1、意外的全局变量;上文我们提到了全局变量不会被当成垃圾回收,我们在编码中有时会出现下面这种情况:

function foo() {

this.bar2 = '默认绑定this指向全局' // 全局变量=> window.bar2

bar = '全局变量'; // 没有声明变量 实际上是全局变量=>window.bar

}

foo();

当我们使用默认绑定,this会指向全局,this.something也会创建一个全局变量,这一点可能很多人没有注意到。

解决方法:在函数内使用严格模式or细心一点

function foo() {

"use strict";

this.bar2 = "严格模式下this指向undefined";

bar = "报错";

} foo();

当然我们也可以手动释放全局变量的内存:window.bar = undefined delete window.bar2

2、被遗忘的定时器和回调函数:不需要setInterval或者setTimeout时,定时器没有被clear,定时器的回调函数以及内部依赖的变量都不能被回收,造成内存泄漏。

解决方法: 在定时器完成工作的时候,手动清除定时器

3、闭包:闭包可以维持函数内局部变量,使其得不到释放,造成内存泄漏。

function bindEvent() {

var obj = document.createElement("XXX");

var unused = function () {

console.log(obj,'闭包内引用obj obj不会被释放');

};

// obj = null;

}

 

解决方法:手动解除引用,obj = null

4、循环引用问题:就是IE9以下的循环引用问题,上文讲过了。

5、没有清理DOM元素引用:

var refA = document.getElementById('refA');

document.body.removeChild(refA); // dom删除了

console.log(refA, "refA"); // 但是还存在引用 能console出整个div 没有被回收

解决办法:refA = null;

6、console保存大量数据在内存中:过多的console,比如定时器的console会导致浏览器卡死。

解决:合理利用console,线上项目尽量少的使用console,当然如果你要发招聘除外。

如何避免内存泄漏:

记住一个原则:不用的东西,及时归还,毕竟你是'借的'嘛。

  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
  2. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
  3. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值