JS引擎之内存管理

内存的生命周期

  • 分配你所需要的内存
  • 使用分配到的内存(读、写)
  • 不需要时将其释放\归还

内存空间

JS内存空间分为栈(stack)、堆(heap)。

基础数据类型

JS中的基础数据类型(Number String Null Undefined Boolean ),这些值都有固定的大小,往往都保存在栈内存中(闭包除外),由系统自动分配存储空间。我们可以直接操作保存在栈内存空间的值,因此基础数据类型都是按值访问 数据在栈内存中的存储与使用方式类似于数据结构中的堆栈数据结构,遵循后进先出的原则。

引用数据类型

JS的引用数据类型,比如数组Array,它们值的大小是不固定的。引用数据类型的值是保存在堆内存中的对象。JS不允许直接访问堆内存中的位置,因此我们不能直接操作对象的堆内存空间。在操作对象时,实际上是在操作对象的引用而不是实际的对象。因此,引用类型的值都是按引用访问的。这里的引用,我们可以粗浅地理解为保存在栈内存中的一个地址,该地址与堆内存的实际值相关联。

因此当我们要访问堆内存中的引用数据类型时,实际上我们首先是从栈中获取了该对象的地址引用(或者地址指针),然后再从堆内存中取得我们需要的数据。

堆与栈的大小

程序运行时,每个线程分配一个stack,每个进程分配一个heap,也就是说,stack是线程独占的,heap是线程共用的。此外,stack创建的时候,大小是确定的,数据超过这个大小,就发生stack overflow错误,而heap的大小是不确定的,需要的话可以不断增加。所以这里只看stack的大小限制。

JavaScript 的内存分配

值的初始化

JavaScript 在定义变量时就完成了内存分配。例如下列代码

var n = 123; // 给数值变量分配内存
var s = "abc"; // 给字符串分配内存

var o = {
  a: 1,
  b: null
}; // 给对象及其包含的值分配内存

// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"]; 

function f(a){
  return a + 2;
} // 给函数(可调用的对象)分配内存

// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
  someElement.style.backgroundColor = 'blue';
}, false);

通过函数调用分配内存

有些函数调用结果是分配对象内存:

var d = new Date(); // 分配一个 Date 对象

var e = document.createElement('div'); // 分配一个 DOM 元素

内存使用

使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。

垃圾回收

垃圾回收机制介绍

找出那些不再继续使用的值,然后释放其占用的内存。垃圾收集器会每隔固定的时间段就执行一次释放操作。 在JavaScript中,最常用的是通过标记清除的算法来找到哪些对象是不再继续使用的,因此 a = null 其实仅仅只是做了一个释放引用的操作,让 a 原本对应的值失去引用,脱离执行环境,这个值会在下一次垃圾收集器执行操作时被找到并释放。

  • 在局部作用域中,当函数执行完毕,局部变量也就没有存在的必要了,因此垃圾收集器很容易做出判断并回收。但是全局变量什么时候需要自动释放内存空间则很难判断,因此在我们的开发中,需要尽量避免使用全局变量,以确保性能问题。

  • 以Google的V8引擎为例,在V8引擎中所有的JAVASCRIPT对象都是通过堆来进行内存分配的。当我们在代码中声明变量并赋值时,V8引擎就会在堆内存中分配一部分给这个变量。如果已申请的内存不足以存储这个变量时,V8引擎就会继续申请内存,直到堆的大小达到了V8引擎的内存上限为止(默认情况下,V8引擎的堆内存的大小上限在64位系统中为1464MB,在32位系统中则为732MB)。

  • 另外,V8引擎对堆内存中的JAVASCRIPT对象进行分代管理。新生代:新生代即存活周期较短的JAVASCRIPT对象,如临时变量、字符串等; 老生代:老生代则为经过多次垃圾回收仍然存活,存活周期较长的对象,如主控制器、服务器对象等。

垃圾回收算法

引用计数算法

对象有没有其他对象引用到。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

// 创建一个对象person,他有两个指向属性age和name的引用
var person = {
    age: 12,
    name: 'aaaa'
};

person.name = null; // 虽然设置为null,但因为person对象还有指向name的引用,因此name不会回收

var p = person; 
person = 1;         //原来的person对象被赋值为1,但因为有新引用p指向原person对象,因此它不会被回收

p = null;           //原person对象已经没有引用,很快会被回收

引用计数对于循环引用产生的bug,两个对象被创建,并互相引用,形成了一个循环。它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。例如IE 6, 7中:

var div;
window.onload = function(){
  div = document.getElementById("myDivElement");
  div.circularReference = div;
  div.lotsOfData = new Array(10000).join("*");
};

在上面的例子里,myDivElement 这个 DOM 元素里的 circularReference 属性引用了 myDivElement,造成了循环引用。如果该属性没有显示移除或者设为 null,引用计数式垃圾收集器将总是且至少有一个引用,并将一直保持在内存里的 DOM 元素,即使其从DOM 树中删去了。如果这个 DOM 元素拥有大量的数据 (如上的 lotsOfData 属性),而这个数据占用的内存将永远不会被释放

标记清除算法

假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

内存泄漏

对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。 不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)

你可能感兴趣的:(JS引擎之内存管理)