跟我学习javascript的垃圾回收机制与内存管理


Posted in Javascript onNovember 23, 2015

一、垃圾回收机制—GC

Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。

原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。

JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行

不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束,而闭包中由于内部函数的原因,外部函数并不能算是结束。

还是上代码说明吧:

function fn1() {
 var obj = {name: 'hanzichi', age: 10};
}

function fn2() {
 var obj = {name:'hanzichi', age: 10};
 return obj;
}

var a = fn1();
var b = fn2();

我们来看代码是如何执行的。首先定义了两个function,分别叫做fn1和fn2,当fn1被调用时,进入fn1的环境,会开辟一块内存存放对象{name: 'hanzichi', age: 10},而当调用结束后,出了fn1的环境,那么该块内存会被js引擎中的垃圾回收器自动释放;在fn2被调用的过程中,返回的对象被全局变量b所指向,所以该块内存并不会被释放。

这里问题就出现了:到底哪个变量是没有用的?所以垃圾收集器必须跟踪到底哪个变量没用,对于不再有用的变量打上标记,以备将来收回其内存。用于标记的无用变量的策略可能因实现而有所区别,通常情况下有两种实现方式:标记清除和引用计数。引用计数不太常用,标记清除较为常用。

二、标记清除

js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

function test(){
 var a = 10 ; //被标记 ,进入环境 
 var b = 20 ; //被标记 ,进入环境
}
test(); //执行完毕 之后 a、b又被标离开环境,被回收。

垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。

到目前为止,IE、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

三、引用计数

引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。这样,当垃圾回收器下次再运行时,它就会释放那些引用次数为0的值所占用的内存。

function test(){
 var a = {} ; //a的引用次数为0 
 var b = a ; //a的引用次数加1,为1 
 var c =a; //a的引用次数再加1,为2
 var b ={}; //a的引用次数减1,为1
}

Netscape Navigator3是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。

function fn() {
 var a = {};
 var b = {};
 a.pro = b;
 b.pro = a;
}

fn();

以上代码a和b的引用次数都是2,fn()执行完毕后,两个对象都已经离开环境,在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收内存,如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。

我们知道,IE中有一部分对象并不是原生js对象。例如,其内存泄露DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。

var element = document.getElementById("some_element");
var myObject = new Object();
myObject.e = element;
element.o = myObject;

这个例子在一个DOM元素(element)与一个原生js对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;而变量element也有一个属性名为o回指myObject。由于存在这个循环引用,即使例子中的DOM从页面中移除,它也永远不会被回收。

看上面的例子,有同学回觉得太弱了,谁会做这样无聊的事情,其实我们是不是就在做

window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
};

这段代码看起来没什么问题,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法会引用外部环境中德变量,自然也包括obj,是不是很隐蔽啊。

解决办法

最简单的方式就是自己手工解除循环引用,比如刚才的函数可以这样

myObject.element = null;
element.o = null;

window.onload=function outerFunction(){
 var obj = document.getElementById("element");
 obj.onclick=function innerFunction(){};
 obj=null;
};

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾回收器下次运行时,就会删除这些值并回收它们占用的内存。

要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变

四、内存管理

1、什么时候触发垃圾回收?

垃圾回收器周期性运行,如果分配的内存非常多,那么回收工作也会很艰巨,确定垃圾回收时间间隔就变成了一个值得思考的问题。IE6的垃圾回收是根据内存分配量运行的,当环境中存在256个变量、4096个对象、64k的字符串任意一种情况的时候就会触发垃圾回收器工作,看起来很科学,不用按一段时间就调用一次,有时候会没必要,这样按需调用不是很好吗?但是如果环境中就是有这么多变量等一直存在,现在脚本如此复杂,很正常,那么结果就是垃圾回收器一直在工作,这样浏览器就没法儿玩儿了。

微软在IE7中做了调整,触发条件不再是固定的,而是动态修改的,初始值和IE6相同,如果垃圾回收器回收的内存分配量低于程序占用内存的15%,说明大部分内存不可被回收,设的垃圾回收触发条件过于敏感,这时候把临街条件翻倍,如果回收的内存高于85%,说明大部分内存早就该清理了,这时候把触发条件置回。这样就使垃圾回收工作职能了很多

2、合理的GC方案

1)、Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:

  • (1)遍历所有可访问的对象。
  • (2)回收已不可访问的对象。

2)、GC的缺陷

和其他语言一样,javascript的GC策略也无法避免一个问题:GC时,停止响应其他操作,这是为了安全考虑。而Javascript的GC在100ms甚至以上,对一般的应用还好,但对于JS游戏,动画对连贯性要求比较高的应用,就麻烦了。这就是新引擎需要优化的点:避免GC造成的长时间停止响应。

3)、GC优化策略

David大叔主要介绍了2个优化方案,而这也是最主要的2个优化方案了:

(1)分代回收(Generation GC)
这个和Java回收策略思想是一致的。目的是通过区分“临时”与“持久”对象;多回收“临时对象”区(young generation),少回收“持久对象”区(tenured generation),减少每次需遍历的对象,从而减少每次GC的耗时。如图:

跟我学习javascript的垃圾回收机制与内存管理

这里需要补充的是:对于tenured generation对象,有额外的开销:把它从young generation迁移到tenured generation,另外,如果被引用了,那引用的指向也需要修改。

(2)增量GC
这个方案的思想很简单,就是“每次处理一点,下次再处理一点,如此类推”。如图:

跟我学习javascript的垃圾回收机制与内存管理

这种方案,虽然耗时短,但中断较多,带来了上下文切换频繁的问题。

因为每种方案都其适用场景和缺点,因此在实际应用中,会根据实际情况选择方案。

比如:低 (对象/s) 比率时,中断执行GC的频率,simple GC更低些;如果大量对象都是长期“存活”,则分代处理优势也不大。

参考:

以上就是关于javascript的垃圾回收机制与内存管理的全部内容,希望对大家的学习有所帮助。

Javascript 相关文章推荐
用javascript实现改变TEXTAREA滚动条和按钮的颜色,以及怎样让滚动条变得扁平
Apr 20 Javascript
jQuery的实现原理的模拟代码 -3 事件处理
Aug 03 Javascript
基于json的jquery地区联动效果代码
Jul 06 Javascript
jquery选择checked在ie8普通模式下的问题
Feb 12 Javascript
jquery获取复选框被选中的值
Apr 10 Javascript
js检测用户输入密码强度
Oct 22 Javascript
JQuery Mobile实现导航栏和页脚
Mar 09 Javascript
JavaScript实现拖拽元素对齐到网格(每次移动固定距离)
Nov 30 Javascript
js仿拉勾网首页穿墙广告效果
Mar 08 Javascript
基于javaScript的this指向总结
Jul 22 Javascript
windows下更新npm和node的方法
Nov 30 Javascript
微信小程序自定义支持图片的弹窗
Dec 21 Javascript
跟我学习javascript解决异步编程异常方案
Nov 23 #Javascript
jQuery实现选中弹出窗口选择框内容后赋值给文本框的方法
Nov 23 #Javascript
Bootstrap每天必学之表单
Nov 23 #Javascript
jquery制作属于自己的select自定义样式
Nov 23 #Javascript
基于jquery实现省市联动效果
Nov 23 #Javascript
jquery实现加载进度条提示效果
Nov 23 #Javascript
使用jquery实现鼠标滑过弹出更多相关信息层附源码下载
Nov 23 #Javascript
You might like
php 文件夹删除、php清除缓存程序
2009/08/25 PHP
解析php中array_merge与array+array的区别
2013/06/21 PHP
php将session放入memcached的设置方法
2014/02/14 PHP
php命令行写shell实例详解
2018/07/19 PHP
php上传后台无法收到数据解决方法
2019/10/28 PHP
利用谷歌地图API获取点与点的距离的js代码
2012/10/11 Javascript
如何阻止复制剪切和粘贴事件为了表单内容的安全
2013/05/23 Javascript
Jquery 复选框取值兼容FF和IE8(测试有效)
2013/10/29 Javascript
JS、CSS加载中的小问题探讨
2013/11/26 Javascript
IE6/IE7中JavaScript json提示缺少标识符、字符串或数字问题处理
2014/12/16 Javascript
js图片轮播手动切换效果
2015/11/10 Javascript
JavaScript实现iframe自动高度调整和不同主域名跨域
2016/02/27 Javascript
Vue监听数据对象变化源码
2017/03/09 Javascript
MvcPager分页控件 适用于Bootstrap
2017/06/03 Javascript
把JavaScript代码改成ES6语法不完全指南(分享)
2017/09/10 Javascript
解决vue-router进行build无法正常显示路由页面的问题
2018/03/06 Javascript
vue以组件或者插件的形式实现throttle或者debounce
2019/05/22 Javascript
vue集成openlayers加载geojson并实现点击弹窗教程
2020/09/24 Javascript
[02:12]打造更好的电竞完美世界:完美盛典回顾篇
2018/12/19 DOTA
Python3.6+selenium2.53.6自动化测试_读取excel文件的方法
2019/09/06 Python
调用其他python脚本文件里面的类和方法过程解析
2019/11/15 Python
python实现视频读取和转化图片
2019/12/10 Python
Django封装交互接口代码
2020/07/12 Python
Python如何读写二进制数组数据
2020/08/01 Python
CSS3制作日历实现代码
2012/01/21 HTML / CSS
新闻记者实习自我鉴定
2013/09/19 职场文书
优秀研究生自我鉴定
2013/12/04 职场文书
生产部经理岗位职责
2013/12/16 职场文书
问卷调查计划书
2014/01/10 职场文书
养殖项目策划书范文
2014/01/13 职场文书
入股协议书范本
2014/04/14 职场文书
公司聚餐通知
2015/04/22 职场文书
劳动争议仲裁代理词
2015/05/25 职场文书
2016春季运动会通讯稿
2015/07/18 职场文书
关于运动会的广播稿
2015/08/19 职场文书
2016年大学生社区服务活动总结
2016/04/06 职场文书