论坛首页 Web前端技术论坛

Javascript 垃圾收集机制

浏览 13860 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (3) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-10-14  

经常使用 Javascript 的人会琢磨其垃圾收集机制,Javascript 并不像 C,C++ 那样需要开发者手动去清除垃圾,在编写 Javascript 程序是,开发者无需关心内存使用问题,所需内存分配以及无用内存(垃圾)的回收完全实现了自动管理。究其根源,主要是程序收集那些不再使用的变量,并且释放其占用的内存。因此,垃圾收集机制会按照固定时间间隔,周期性反复的执行这一操作。


举例来说,局部变量只存在于函数内部,程序会为局部变量在栈内存或堆内存中分配对应的存储空间,当函数运行结束,局部变量所占用的内存就没有存在的必要了,这时程序会释放局部变量所占用的内存供其他变量使用。这是程序最简单释放内存的方法,但是很多时候,程序中变量会一直被使用,此时垃圾收集机制必须跟踪变量并且判断其是否被使用,是否可以释放其内存空间。

垃圾收集机制主要判断变量释放内存空间的方法有两个:其一是标记清除法,其二是引用计数法。

标记法,每个变量都有其运行环境,变量创建后会在某种环境中运行,比如创建一个局部变量,局部变量会运行在函数体内。当函数运行时,会标记局部变量为“进入环境”,当函数体运行结束后,意味着变量脱离了其运行环境,此时则将变量标记为“离开环境”。对于“离开环境”的变量,垃圾收集机制会进行相应记录,并且在下一个回收周期时将其释放。

引用计数法,跟踪记录每个值的被引用次数。声明一个变量并将一个引用类型值赋给该变量时,这个值得引用次数就是 1。如果同一个值又被赋给另外一个变量,则该值的引用次数加 1。相反,如果包含对这个值的引用的变量又取得另外一个值,这个值得引用次数减 1。当这个值得引用次数为 0 时,则说明没有办法再访问到此值,因此就可以将其占用的内存空间回收。当垃圾收集器在下一个周期运行时,会释放引用次数为零的值所占用的内存空间。(原文解释参考:Javascript 高级程序设计 - 第二版)

举个例子来说:
            function countMethod(){
                  var object1 = new Object(); // 声明变量,计数器由 0 变为 1
                  var object2 = new Object(); // 声明变量,计数器由 0 变为 1
                  object1.method1 = object2;  // object1 计数器 -1,object2 计数器 +1
                  object2.method2 = object1;  // object1 计数器 +1,object2 计数器 -1
            }
此函数运行退出后,object1 的计数器读数为 1,object2 的计数器度数为 1。所以两个变量都不会被销毁。如果大量的这样的程序存在于函数体内,就会导致大量的内存被浪费而无法回收,从而导致内存的泄露。

上述问题解决方法,手动释放 object1 object2 所占用的内存。即:
                 object1.method1 = null;
                 object2.method2 = null;

对比上面的例子,举一个正常情况下的例子。
            function countMethod(){
                  var object1 = new Object(); // 声明变量,计数器由 0 变为 1
                  var object2 = new Object(); // 声明变量,计数器由 0 变为 1
                  object1.method1 = "This is object1";  // object1 计数器 -1,object1 读数变为0
                  object2.method2 = "This is object2";  // object2 计数器 -1,object2 读数变为0
            }
通过上例看出,正常情况下,当函数运行结束后,object1 object2的读数均为 0,在下一个垃圾收集周期时,会被回收并且释放其所占用的内存。

   发表时间:2011-10-14  
学习了。javacript有没有好书介绍下。
0 请登录后投票
   发表时间:2011-10-14  
foible 写道
学习了。javacript有没有好书介绍下。

 

我个人觉得如果有一定的javascript基础后,去看 《javascript 高级程序设计》- 尼古拉斯·泽卡斯(Nicholas C.Zakas) 就可以了。这哥们写的书挺好的。

0 请登录后投票
   发表时间:2011-10-14   最后修改:2011-10-14
好像说的有问题吧?
我记得标记法是这样:
从全局开始逐一标记能引用到的各个对象,标记完成后,没有被标记的就是永远不可能被用到的对象,就会被释放。

你对引用计数举的例子分析也有些问题,说到变量就不能脱离作用域链(scope chain),及当前执行环境(activate object)
以你的例子来说:
function countMethod(){
	// 进入新的函数,当前activate object及作用域链为: activate object = (scope1) -> global
	// (scope1)引用数为1, global引用数+1
	var object1 = new Object(); 
	// 新建的对象(以o1称它)被当前作用域引用:(scope1).object1 = o1
	// o1引用数为1
	var object2 = new Object(); 
	// 新建的对象(以o2称它)被当前作用域引用:(scope1).object2 = o2
	// o2引用数为1
	object1.method1 = object2;  // object1 计数器 -1,object2 计数器 +1
	// 创建o2引用: o1.method1 = object2
	// o2引用数为2
	object2.method2 = object1;  // object1 计数器 +1,object2 计数器 -1
	// 创建o1引用: o2.method2 = o1
	// o1引用数为2
	// 离开countMethod,当前activate object恢复调用前,即: activate object = global
	// (scope1)引用数为0,释放它,同时它的两个属性object1与object2引用也释放,导致o1 o2引用计算-1
	// o1引用数为1,o2引用数为1  <-- 这是最简单的循环引用
}
// 定义完countMethod方法(以f1称它),产生两个引用: global.countMethod = f1, f1.[[scope]] = global
// f1引用数为1, global引用数+1
// 当前activate object及作用域链为: activate object = global
countMethod();



如果有闭包,根据引用计数就是这样子
function f1(){
	// activate object = (scope1) -> global
	// (scope1)+1=1, global+1
	var a = {};
	// (scope1).a = o1
	// o1+1=1
	return function f2(){}
	// (scope1).f2=f2, f2.[[scope]]=(scope1)
	// f2+1=1; (scope1)+1=2
	// activate object=global
	// (scope1)-1=1  <-- 还有一个引用,是f2造成的,函数定义会存储当前的作用域
}
// global.f1 = f1, f1.[[scope]] = global
// f1+1=1, global+1
var b = f1();
// global.b=f2
// f2+1=2
b = null;
// global.b=null
// f2-1=1
// 此时也是有循环引用,(scope1).f2 = f2, f2.[[scope]] = (scope1)


以上是纯理论上的最简单的引用计数,但是可以看到,非常脆弱。现在不管什么浏览器都是几种方法结合,JS内部的循环引用都能识别。
我们常说的循环引用无法释放是指JS与DOM两套体系之间的循环引用
0 请登录后投票
   发表时间:2011-10-14  

“从全局开始逐一标记能引用到的各个对象,标记完成后,没有被标记的就是永远不可能被用到的对象,就会被释放。” 这句话没错,但是我认为你所说的标记完成,也是对于变量(全局,局部)是否“进入环境”的另外一种说法。变量进入环境,则标记,未进入环境或函数运行完毕后退出环境之后,都会标记为“离开环境”。我们可以从程序获得所有的变量(全局,局部),但是如果没有函数的运行,又怎能确定哪些变量是有用的,从而进行“标记”呢?所以我认为变量“进出环境”为先导条件,有了这个才有了“标记”。没有函数运行环境,无法谈标记。感谢你的回复。相互交流哈。

 

计数法在IE的支持不是很好,研究的也不多,非常感谢你的指出哈。学习了。谢谢。

0 请登录后投票
   发表时间:2011-10-14   最后修改:2011-10-14
我学JS时间不长,但是我想理解了这段话。
大概也能知道JS的垃圾机制了吧。

We described it as a list of
objects, not a stack of bindings. Each time a JavaScript function is invoked, a new object
is created to hold the local variables for that invocation, and that object is added to the
scope chain. When the function returns, that variable binding object is removed from
the scope chain. If there were no nested functions, there are no more references to the
binding object and it gets garbage collected. If there were nested functions defined,
then each of those functions has a reference to the scope chain, and that scope chain
refers to the variable binding object. If those nested functions objects remained within
their outer function, however, then they themselves will be garbage collected, along
with the variable binding object they referred to. But if the function defines a nested
function and returns it or stores it into a property somewhere, then there will be an
external reference to the nested function. It won’t be garbage collected, and the variable
binding object it refers to won’t be garbage collected either.

出自 JavaScript.The.Definitive.Guide.6th.Edition
0 请登录后投票
   发表时间:2011-10-14  
firefly_zp 写道

“从全局开始逐一标记能引用到的各个对象,标记完成后,没有被标记的就是永远不可能被用到的对象,就会被释放。” 这句话没错,但是我认为你所说的标记完成,也是对于变量(全局,局部)是否“进入环境”的另外一种说法。变量进入环境,则标记,未进入环境或函数运行完毕后退出环境之后,都会标记为“离开环境”。我们可以从程序获得所有的变量(全局,局部),但是如果没有函数的运行,又怎能确定哪些变量是有用的,从而进行“标记”呢?所以我认为变量“进出环境”为先导条件,有了这个才有了“标记”。没有函数运行环境,无法谈标记。感谢你的回复。相互交流哈。

 

计数法在IE的支持不是很好,研究的也不多,非常感谢你的指出哈。学习了。谢谢。

 

假如我的脚本就一行

 

a = 1

 

那执行完后,这个a是“进入环境”还是“离开环境”状态呢?

0 请登录后投票
   发表时间:2011-10-14   最后修改:2011-10-14
kidneyball 写道
firefly_zp 写道

“从全局开始逐一标记能引用到的各个对象,标记完成后,没有被标记的就是永远不可能被用到的对象,就会被释放。” 这句话没错,但是我认为你所说的标记完成,也是对于变量(全局,局部)是否“进入环境”的另外一种说法。变量进入环境,则标记,未进入环境或函数运行完毕后退出环境之后,都会标记为“离开环境”。我们可以从程序获得所有的变量(全局,局部),但是如果没有函数的运行,又怎能确定哪些变量是有用的,从而进行“标记”呢?所以我认为变量“进出环境”为先导条件,有了这个才有了“标记”。没有函数运行环境,无法谈标记。感谢你的回复。相互交流哈。

 

计数法在IE的支持不是很好,研究的也不多,非常感谢你的指出哈。学习了。谢谢。

 

假如我的脚本就一行

 

a = 1

 

那执行完后,这个a是“进入环境”还是“离开环境”状态呢?

 

 

执行完了,还有环境吗?

 

0 请登录后投票
   发表时间:2011-10-15  
firefly_zp 写道

“从全局开始逐一标记能引用到的各个对象,标记完成后,没有被标记的就是永远不可能被用到的对象,就会被释放。” 这句话没错,但是我认为你所说的标记完成,也是对于变量(全局,局部)是否“进入环境”的另外一种说法。变量进入环境,则标记,未进入环境或函数运行完毕后退出环境之后,都会标记为“离开环境”。我们可以从程序获得所有的变量(全局,局部),但是如果没有函数的运行,又怎能确定哪些变量是有用的,从而进行“标记”呢?所以我认为变量“进出环境”为先导条件,有了这个才有了“标记”。没有函数运行环境,无法谈标记。感谢你的回复。相互交流哈。

 

计数法在IE的支持不是很好,研究的也不多,非常感谢你的指出哈。学习了。谢谢。

 

我还是比较同意你的观点的。

个人理解,函数在未执行前,只是一个定义而已,分配的内存应该只是代码段。只有在当函数执行时,其中的内部变量才会绑定到一个对象,然后添加到scope  chain中。

 

 

0 请登录后投票
   发表时间:2011-10-15  
psychopath 写道
kidneyball 写道
firefly_zp 写道

“从全局开始逐一标记能引用到的各个对象,标记完成后,没有被标记的就是永远不可能被用到的对象,就会被释放。” 这句话没错,但是我认为你所说的标记完成,也是对于变量(全局,局部)是否“进入环境”的另外一种说法。变量进入环境,则标记,未进入环境或函数运行完毕后退出环境之后,都会标记为“离开环境”。我们可以从程序获得所有的变量(全局,局部),但是如果没有函数的运行,又怎能确定哪些变量是有用的,从而进行“标记”呢?所以我认为变量“进出环境”为先导条件,有了这个才有了“标记”。没有函数运行环境,无法谈标记。感谢你的回复。相互交流哈。

 

计数法在IE的支持不是很好,研究的也不多,非常感谢你的指出哈。学习了。谢谢。

 

假如我的脚本就一行

 

a = 1

 

那执行完后,这个a是“进入环境”还是“离开环境”状态呢?

 

 

执行完了,还有环境吗?

 

变量声明是在什么地方声明的,是函数体内局部变量还是全局变量?如果是局部变量必然会有其运行环境(子函数内,闭包函数内等等),在包含此局部变量的函数运行结束后(变量 a 没有被其他变量引用),就会被释放。如果是全局变量,当包含这个全局变量的函数体运行结束后,这个变量才会被释放,否则一直在环境内,不会释放的。

 

单说 a = 1 这个函数声明(如果只有这一行,那就不是 js 了。他必然包含在某个函数中 - 或全局函数或内部函数),a = 1 执行完成后是“进入环境”,因为他被包含于某个函数中,当他被包含于的这个函数执行结束后,并且没有其他变量对他的引用时,这个时候的状态才是“离开环境”。

 

我们现在所说的“离开环境”的状态主要有两点:第一,变量本身离开其所处的环境(最直接的可以看出)。第二,如果变量被其他变量所引用,则要去判断引用 a 的变量是否离开了他的环境,如果两点都符合,则此时的 a = 1的状态是“离开环境”。

0 请登录后投票
论坛首页 Web前端技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics