JavaScript自动内存管理与垃圾回收策略详细分析讲解
作者:似曾不相识
自动内存管理
JavaScript编程语言通过自动内存管理实现内存分配和闲置资源回收。
简单来讲就是:只要确定某个变量X不会再被使用了,就将变量X占用的内存进行释放。这种判断是周期性执行的,即:垃圾回收程序隔一定时间就会自动执行一次,以释放某些不必要的内存开支。
JavaScript垃圾回收过程中的难点在于:如何正确判定一块内存是否还有用?
垃圾回收策略
在C/C++程序中,我们记忆比较深刻的可能是它的“指针机制”,因为这些指针的存在,内存管理是靠程序员手动分配和释放的。相较JavaScript的自动内存管理方式,显得更为繁琐和复杂。
但是,如第一部分所讲,JS垃圾回收的难点在于:如何判定一块内存是否还有用?举个简单的例子,以函数call()的调用为例:
<script> function call(){ const name = 'Hello'; console.log(name); } call(); </script>
在调用函数时,会为函数体的执行开辟新的内存空间。call()函数内部定义了局部变量name,并且将其输出在控制台;输出结束,call()函数也执行结束,函数体执行过程退出;此时,不再需要call()函数的局部变量name,那么,name占用的内存就可以被释放了,空出来的内存在之后可以被程序的其它部分使用。
但是,并非所有的内存有用性判定都会如此清晰,假如:在call()函数内部形成了闭包,对变量的生存周期进行了延展处理等等,所以,垃圾回收程序必须去追踪记录变量的状态,以确定:哪个变量还会被使用?哪个变量不会再被使用?以便于内存回收。
而如何标记未使用的变量,也有不同的实现方式。在浏览器的发展史上,用到过两种如下的主要标记策略。
标记清理策略
JavaScript最常用的垃圾回收策略是“标记清理(mark-and-sweep)”。
基本思路是:当变量进入上下文,比如:函数内部声明一个变量时,这个变量就会被加载存在于上下文中的标记。而不在上下文中的变量,逻辑上讲,永远不应该释放它们的内存,因为只要上下文的代码在运行,就有可能用到这些不在上下文中的变量。此外,当变量离开上下文时,也会被加上离开上下文的标记。
在垃圾回收程序运行的时候,会使用新的标记符号标记内存中存储的所有变量。然后,将所有在上下文中的变量、以及被在上下文中的变量引用的变量上面的标记去掉,经过一轮筛选,剩下的仍然带有特殊标记的变量就是将要被删除的垃圾了。原因在于:这些带有特殊标记的变量在任何上下文中的都不会被使用到了。接着,垃圾回收程序执行一次内存清理,销毁带标记的所有值并回收它们的内存。
至2008年,IE、FireFox、Opera、Chrome和Safari都在自己的JavaScript实现中采用这种标记清理策略,只是在实现细节上有所不同。
引用计数策略
“引用计数(reference counting)”策略不太常用,其思路是:对每个值都记录它被引用的次数。例如:声明一个变量X并给X赋值为999,此时,这个值999的引用次数就为1.如果同一个值999又被赋值给另一个变量Y,那么值999的引用次数+1=2.类似的,如果保存该值999的引用变量X被其它值(如:null)给覆盖掉了,那么值999的引用次数就-1=1.
而当一个值的引用数为0时,就说明没有任何变量引用到这个值了,就可以安全地收回内存了。当垃圾回收程序下一次执行时,就可以释放引用数为0的值所在的内存空间。
但是,这种策略存在“循环引用”的问题,导致某些值的引用数永远不会变为零,也就没有机会被清理掉,引发内存泄露。
内存管理技巧
在使用垃圾回收的编程环境中,开发者通常无需关心内存管理。但是,JavaScript运行在一个内存管理和垃圾回收都十分特殊的环境中。因为操作系统可分配给承载JavaScript程序的网页程序的内存量是十分有限的,这种内存限制不仅影响变量分配,同时也影响调用栈以及能够在同一个线程中执行的语句数量。
将网页程序的内存占用量保持在一个较小的值,可以让页面的性能更优。
解除引用
优化内存占用的最佳手段就是保证在程序运行过程中只保存必要的数据,如果数据不再必要,那么就将其设置为null,从而释放其引用,这可称为“解除引用”。这个技巧最适合用于全局变量和全局对象的属性上,因为局部变量在超出最大作用域之后,就会被自动解除引用。
如下,函数createObject()执行完毕,会返回一个Object对象,同时内部的局部变量obj会自动解除与new Object()对象之间的引用关系;而在全局范围内,globalObject对象获取到了Object对象的引用;随后,当该对象不再被使用时,就应当由开发者主动解除引用。
//创建一个对象 function createObject(name){ let obj = new Object(); obj.name = name; return obj; } //全局变量 const globalObject = createObject("Tom"); //当该对象不再被使用时,进行解除引用 globalObject = null;
解除对一个值的引用的并不会自动导致相关的内存被回收,而是在于,解除引用可以保证相关的值不再存在于上下文环境中了,这样,垃圾回收程序下一次执行时,这个值所在的内存空间就会被回收。
const和let变量声明
ES6新增的关键字const、let,不仅有助于改善代码风格,而且同样有助于垃圾回收的过程。因为const和let都以块(而非函数)为作用域,所以相比于var,使用这两个关键字,可能会更早的让垃圾回收程序介入,尽早回收掉应该回收的内存。
到此这篇关于JavaScript自动内存管理与垃圾回收策略详细分析讲解的文章就介绍到这了,更多相关JS自动内存管理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!