javascript中的内存管理和垃圾回收
前面的话
不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存;然后,使用分配到的内存;最后,释放其内存。而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制。本文将详细介绍javascript中的内存管理和垃圾回收
分配内存
为了不让程序员费心分配内存,JavaScript 在定义变量时就完成了内存分配
var n = ; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存
var o = {a: ,b: null}; // 给对象及其包含的值分配内存
有些函数调用结果是分配对象内存
var d = new Date(); // 分配一个 Date 对象
var e = document.createElement('div'); // 分配一个 DOM 元素
有些方法分配新变量或者新对象
var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新数组有四个元素,是 a 连接 a2 的结果
【存储方式】
因为原始值占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈(stack)中
由于复杂值的大小会改变,所以不能将其存放在栈中,否则会降低变量查询速度,因此其存储在堆(heap)中,存储在变量处的值是一个指针,指向存储对象的内存处
使用内存
使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数
var a = ;
console.log(a);// 读取内存中的值
a = 2; // 写入内存
释放内存
大多数内存管理的问题都在这个阶段。在这里最艰难的任务是找到“所分配的内存确实已经不再需要了”
Javascript内嵌了垃圾收集器,用来跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。垃圾收集器会按照固定的时间间隔,或代码执行中预定的收集时间,周期性地执行这一操作
局部变量只在函数执行的过程中存在。而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。然后在函数中使用这些变量,直到函数执行结束。此时,局部变量就没有存在的必要了。因此可以释放它们的内存以供将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但并非所有情况下都这么容易就能得出结论
垃圾收集器必须跟踪哪个变量有用哪个变量无用,对于不再有用的变量打上标记,以备将来收回其所占用的内存。用于标识无用变量的策略通常有标记清除和引用计数两种
引用计数
引用计数是最简单的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收
下面代码中,两个对象a、b被创建,一个作为另一个的属性被引用,另一个被分配给变量o
var o ={ a: {b:}}
o2引用了o
var o2 = o;
“这个对象”的原始引用o被o2替换了
o = ;
现在,“这个对象”有两个引用了,一个是o2,一个是oa
var oa = o2.a;
最初的对象现在已经是零引用了,然而它的属性a的对象还在被oa引用,所以还不能回收
o2 = "yo";
a属性的那个对象现在也是零引用了,它可以被垃圾回收了
oa = null;
【循环引用】
Netscape Navigator3.0是最早使用引用计数策略的浏览器,但很快它就遇到了一个严重的问题——循环引用
引用计数算法有个限制:无法处理循环引用。在下面的例子中,两个对象被创建,并互相引用,形成了一个循环。它们被调用之后不会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收
function f(){
var o = {};
var o2 = {};
o.a = o2; // o 引用 o2
o2.a = o; // o2 引用 o
return "azerty";
}
f();
【IE低版本】
IE8-浏览器中,有一部分对象并不是原生javascript对象,例如,其BOM和DOM中的对象就是使用c++以COM(component Object Model 组件对象模型)对象的形式实现,而COM对象的垃圾回收机制采用的就是引用计数策略。该方式常常造成对象被循环引用时内存发生泄漏
function f(){
var element = document.getElementById('some_element');
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
}
fn()
这个例子在一个DOM元素(element)与一个原生javascript对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象,而变量element也有一个属性名为someObject的属性指向myObject。由于存在这个循环引用,即使将例子中的DOM从页面中移除,它也永远不会被回收
为了避免类似这样的循环引用,最好是在不使用它们的时候手工断开原生javascript和DOM元素之间的连接
myObject.element = null;
element.someObject = null;
将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存
为了解决此问题,IE9把BOM和DOM对象都转换成了真正的javascript对象
标记清除
javascript中最常用的垃圾收集算法是标记清除(mark-and-sweep),这个算法把“对象是否不再需要”简化定义为“对象是否可以到达”。如果对象不可到达,对象将被垃圾回收机制回收
大多数浏览器实现使用的都是标记清除式的垃圾收集策略,只不过垃圾收集的时间互有不同
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以到达的对象和所有不能到达的对象
该算法称为标记清除,是因为分为标记(mark)和清除(sweep)两个阶段
在标记阶段,垃圾回收器会从根对象开始遍历,每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象
在清除阶段,垃圾回收器会对内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作
在标记阶段,从根对象1可以访问到B,从B又可以访问到E,那么B和E都是可到达对象,同样的道理,F、G、J和K都是可到达对象。在回收阶段,所有未标记为可到达的对象都会被垃圾回收器回收
【循环引用】
使用标记清除算法,循环引用不再是问题,上面的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收
性能问题
垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。在这种情况下,确定垃圾收集时间间隔是一个非常重要的问题
IE的垃圾收集器是根据内存分配量运行的。具体一点说,就是256个变量,4096个对象(或数组)字面量和数组元素(slot)或者64kb的字符串。达到上述任何一个临界值,垃圾收集器就会运行
这种实现方式的问题在于,如果一个脚本中包含那么多变量,那么该脚本很可能会在其生命周期中一直保有那么多的变量。而这样一来,垃圾收集器就不得不频繁地运行。结果,由此引发的严重性能问题促使IE7重写了其垃圾收集例程
IE7的javascript引擎的垃圾收集例程改变了工作方式:触发垃圾收集的变量分配、字面量和数组元素的临界值被调整为动态修正。IE7中的各项临界值在初始时与IE6相等。如果垃圾收集例程回收的内存分配量低于15%,则变量、字面量和数组元素的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界值重置回默认值。这样,极大地提升了IE在运行包含大量javascript的页面时的性能
事实上,在有的浏览器中可以触发垃圾收集过程。在IE中,调用window.CollectGarbage()方法会立即执行垃圾收集
优化内存占用
使用具备垃圾收集机制的javascript的主要问题在于:分配给web浏览器的可用内存数量通常要比分配给桌面应用程序的少,目的是防止运行javascript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量
因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式是:为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用,这种做法叫解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时自动被解除引用
function createPerson(name){
var localPerson = new Object();
localPerson.name = name;
return localPerson;
} var globalPerson = createPerson('test');
globalPerson = null;
不过,要注意的是,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收
javascript中的内存管理和垃圾回收的更多相关文章
- 使用虚幻引擎中的C++导论(四-内存管理与垃圾回收)(终)
使用虚幻引擎中的C++导论(四)(终) 第一,这篇是我翻译的虚幻4官网的新手编程教程,原文传送门,有的翻译不太好,但大体意思差不多,请支持我O(∩_∩)O谢谢. 第二,某些细节操作,这篇文章省略了,如 ...
- javascript中的内存管理
目录 简介 内存生命周期 JS中的垃圾回收器 引用计数垃圾回收算法 Mark-and-sweep回收算法 调试内存问题 闭包Closures中的内存泄露 javascript中的内存管理 简介 在c语 ...
- C#内存管理与垃圾回收
垃圾回收还得从根说起,就像生儿育女一样. 根:根是一个位置,存放一个指针,该指针指向托管堆中的一个对象,或是一个空指针不指向任何对象,即为null.根存在线程栈或托管堆中,大部分的跟都在线程栈上,因为 ...
- Java之美[从菜鸟到高手演变]之JVM内存管理及垃圾回收
很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...
- Java内存管理和垃圾回收
笔记,深入理解java虚拟机 Java运行时内存区域 程序计数器,线程独占,当前线程所执行的字节码的行号指示器,每个线程需要记录下执行到哪儿了,下次调度的时候可以继续执行,这个区是唯一不会发生oom的 ...
- 面试题之C# 内存管理与垃圾回收
面试题之C# 内存管理与垃圾回收 你说说C# 的内存管理是怎么样的 这句话我记了一个多礼拜了, 自从上次东北师大面试之后, 具体请看<随便扯扯东北师大的面试>. 国庆闲着没事, 就大概了解 ...
- JVM内存管理及垃圾回收【转】
很多Java面试的时候,都会问到有关Java垃圾回收的问题,提到垃圾回收肯定要涉及到JVM内存管理机制,Java语言的执行效率一直被C.C++程序员所嘲笑,其实,事实就是这样,Java在执行效率方面确 ...
- .NET基础 (05)内存管理和垃圾回收
内存管理和垃圾回收1 简述.NET中堆栈和堆的特点和差异2 执行string abc="aaa"+"bbb"+"ccc"共分配了多少内存3 ...
- JVM原理(Java代码编译和执行的整个过程+JVM内存管理及垃圾回收机制)
转载注明出处: http://blog.csdn.net/cutesource/article/details/5904501 JVM工作原理和特点主要是指操作系统装入JVM是通过jdk中Java.e ...
随机推荐
- Spring源码情操陶冶-PathMatchingResourcePatternResolver路径资源匹配溶解器
本文简单的分析下spring对某个目录下的class资源是如何做到全部的加载 PathMatchingResourcePatternResolver#getResources PathMatching ...
- UOJ#77. A+B Problem [可持久化线段树优化建边 最小割]
UOJ#77. A+B Problem 题意:自己看 接触过线段树优化建图后思路不难想,细节要处理好 乱建图无果后想到最小割 白色和黑色只能选一个,割掉一个就行了 之前选白色必须额外割掉一个p[i], ...
- springIOC、AOP的一些注解
springIOC.AOP的一些注解(使用这些注解之前要导入spring框架的一些依赖): 1.注入IOC容器 @Compontent:使用注解的方式添加到ioc容器需要在配置文件 ...
- canvas常用api
1. 在canvas标签中给出长宽(不带单位):<canvas width="600" height="600"></canvas> 或 ...
- git取消文件跟踪
在使用git的时候,有些文件是不需要上传的,所以就可以修改 .gitignore 例如: 如果是对所有文件都取消跟踪的话,就是 git rm -r -cached . //不删除本地文件 git ...
- 整理的linux面试运维题
如何在非交互模式下把 /home/example/下所有.conf文件中的 192.168.0.2 改成 db01 ? find /home/example/ -type f -name &quo ...
- mysql中的coalesce用法
在mysql中,其实有不少方法和函数是很有用的,这次介绍一个叫coalesce的,拼写十分麻烦,但其实作用是将返回传入的参数中第一个非null的值,比如 SELECT COALESCE(NULL ...
- Java经典编程题50道之二十二
利用递归方法求5!. public class Example22 { public static void main(String[] args) { int n = 5; ...
- jersey2.26+spring5+jpa一步步搭建restful服务
前言 首先,为什么想选择Jersey做restful服务呢?我个人比较喜欢它的插件化设计,可以方便的注入自己的全局处理逻辑.再一个就是可以生成wadl描述文件,供查询服务方法.所以在学习spring的 ...
- 在centos 6.8下安装docker
1.检查自己的系统内核是不是64位系统,因为docker只能安装在64位系统中 命令: uname -a 结果 2.6.32-642.6.2.el6.x86_64 2.查看自己centos的版本 ca ...