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

一、垃圾回收机制—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的耗时。如图:

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

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

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

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

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

参考:

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

(0)

相关推荐

  • JavaScript 垃圾回收机制分析

    在公司经常会听到大牛们讨论时说道内存泄露神马的,每每都惊羡不已,最近精力主要用在了Web 开发上,读了一下<JavaScript高级程序设计>(书名很唬人,实际作者写的特别好,由浅入深)了解了一下JavaScript垃圾回收机制,对内存泄露有了一定的认识. 和C#.Java一样JavaScript有自动垃圾回收机制,也就是说执行环境会负责管理代码执行过程中使用的内存,在开发过程中就无需考虑内存分配及无用内存的回收问题了.JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其

  • 对于js垃圾回收机制的理解

    原理 找到不再被使用的变量,然后释放其占用的内存,但这个过程不是时时的,因为其开销比较大, 所以垃圾回收器会按照固定时间间隔周期性的执行 回收方式 a.标记清除 当变量进入环境时,将这个变量标记为"进入环境";当变量离开环境时,则将其标记为"离开环境". 标记"离开环境"的就回收内存 b.引入计数(低级浏览器) 当变量声明,第一次赋值时记为1,然后当这个变量值改变时,记录为0,将计数为0的回收 内存泄露 a.意外的全局变量引起的内存泄露 原因:

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

    一.垃圾回收机制-GC Javascript具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存. 原理:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存. JavaScript垃圾回收的机制很简单:找出不再使用的变量,然后释放掉其占用的内存,但是这个过程不是实时的,因为其开销比较大,所以垃圾回收器会按照固定的时间间隔周期性的执行. 不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生

  • JavaScript的垃圾回收机制与内存管理

    如果我们想要优化性能,首先我们必须得了解JavaScript中的垃圾回收机制,这样可以将很多没有被使用到的变量从内存中清除掉,腾出更多的内存空间,给别的变量分配内存空间. JavaScript中的垃圾回收机制 引言 本篇文章将讲解一下javascript的垃圾回收机制.同时,我们必须先具备作用域链的概念,不懂的小伙伴可以先花5分钟观看一下这篇文章,简单了解一下作用域链的知识--从零开始讲解JavaScript中作用域链的概念及用途 正文 一.垃圾回收机制 在JavaScript中,具有自动垃圾回

  • 详解JavaScript的垃圾回收机制

    目录 为什么需要垃圾回收(GC) 什么是垃圾回收 垃圾产生 垃圾回收策略 引用计数标记 循环引用引发的问题 解决方法 引用计数算法的优缺点 标记清除算法 核心思想 标记清除算法优缺点 标记整理算法 V8引擎的垃圾回收 回收新生代对象 对象晋升机制 回收老生代对象 参考文档: 总结 为什么需要垃圾回收(GC) 程序和人一样,生活时间长了会产生垃圾,程序在运行过程中也会产生垃圾,垃圾积攒过多后,会导致程序运行速度变慢. 在JavaScript中的字符串.对象.数组等数据的内存是不固定的,只有真正使用

  • 深入理解Java垃圾回收机制以及内存泄漏

    前言 在segmentfault上看到一个问题:java有完善的GC机制,那么在java中是否会出现内存泄漏的问题,以及能否给出一个内存泄漏的案例.本问题视图给出此问题的完整答案. 垃圾回收机制简介 在程序运行过程中,每创建一个对象都会被分配一定的内存用以存储对象数据.如果只是不停的分配内存,那么程序迟早面临内存不足的问题.所以在任何语言中,都会有一个内存回收机制来释放过期对象的内存,以保证内存能够被重复利用. 内存回收机制按照实现角色的不同可以分为两种,一种是程序员手动实现内存的释放(比如C语

  • Android垃圾回收机制解决内存泄露问题

    在android编码中,会有一些简便的写法和编码习惯,会导致我们的代码有很多内存泄露的问题,在这里做一个已知错误的总结: 1.编写单例的时候常出现的错误. 错误方式: public class Foo{ private static Foo foo; private Context mContext; private Foo(Context mContext){ this.mContext = mContext; } // 普通单例,非线程安全 public static Foo getInst

  • 深入解析PHP垃圾回收机制对内存泄露的处理

    上次说到了refcount和is_ref,这里来说说内存泄露的情况 复制代码 代码如下: $a = array(1, 2, &$a);unset($a); 在老的PHP版本中,这里就会出现内存泄露,分析如下: 执行第一行,可以知道$a和$a[2]指向的zval refcount=2,is_ref=1 然后执行第二行,$a将会从符号表中被删除,同时指向的zval的refcount--,此时refcount=1,因为refcount!=0,故此zval不会被当做垃圾回收,但是此时我们却失去了$a[2

  • JavaScript垃圾回收机制(引用计数,标记清除,性能优化)

    目录 一.前言 二.何为垃圾 三.垃圾回收 四.可达性(Reachability) 五.可达性举例 层次关联 相互关联 可达孤岛 六.垃圾回收算法 引用计数 标记清除 七.性能优化 分代回收 增量收集 空闲收集 八.总结 一.前言 垃圾回收是JavaScript的隐藏机制,我们通常无需为垃圾回收劳心费力,只需要专注功能的开发就好了.但是这并不意味着我们在编写JavaScript的时候就可以高枕无忧了,伴随着我们实现的功能越来越复杂,代码量越积越大,性能问题就变的越来越突出.如何写出执行速度更快,

  • Java垃圾回收机制简述

    说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理. 顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为"垃圾"?那么当一些对象被确定为垃圾之后,采用什么样的策略来进行回收(释放空间)?在目前的商业虚拟机中,有哪些典型的垃圾收集器?下面我们就来逐一探讨这些问题.以下是本文的目录大纲: 如何确定某个对象是"

  • 简单谈谈PHP的垃圾回收机制

    1.每一个变量定义时都保存在一个叫zval的容器里面,这里面包含了数量的类型和和值,还包含了一个refcount(理解为存在几个变量个数)和is_ref(理解为是否为引用变量)两个额外信息,当变量被引用一次refcount就会+1,当你unset一下之后这个值就会减1直到为0就会从内存中删除 2.定义一个变量的时候并不是每次都会扩大预定于值,因为PHP会在内存中先预占用一个空间,等你声明变量的时候就会分配给你,但是当你超出这个预占用空间之后,那么它就会增加空间,但是等你删除变量时候这个空间容量不

随机推荐