JavaScript 的内存管理

278 阅读5分钟

不管是什么样的计算机程序语言,运行在对应的代码引擎上,对应的使用内存过程大致逻辑是一样的,可以分为这三个步骤:

分配你所需要的系统内存空间;

使用分配到的内存进行读或者写等操作;

不需要使用内存时,将其空间释放或者归还。

对于简单的数据类型,内存是保存在栈(stack)空间中的;复杂数据类型,内存保存在堆(heap)空间中。简而言之,基本就是说明以下两点。

基本类型: 这些类型在内存中会占据固定的内存空间,它们的值都保存在栈空间中,直接可以通过值来访问这些;

引用类型: 由于引用类型值大小不固定(比如上面的对象可以添加属性等),栈内存中存放地址指向堆内存中的对象,是通过引用来访问的。

因此总结来说:栈内存中的基本类型,可以通过操作系统直接处理;而堆内存中的引用类型,正是由于可以经常变化,大小不固定,因此需要 JavaScript 的引擎通过垃圾回收机制来处理。

Chrome 内存回收机制

在 Chrome 浏览器中,JavaScript 的 V8 引擎被限制了内存的使用,根据不同的操作系统(操作系统有 64 位和 32 位的)内存大小会不同,大的可以到 1.4G 的空间,小的只能到 0.7G 的空间。

那么请你思考一下,为什么要去限制内存使用呢?大致是两个原因:V8 最开始是为浏览器而设计的引擎,早些年由于 Web 应用都比较简单,其实并未考虑占据过多的内存空间;另外又由于被 V8 的垃圾回收机制所限制,比如清理大量的内存时会耗费很多时间,这样会引起 JavaScript 执行的线程被挂起,会影响当前执行的页面应用的性能。

标记清除(Mark-Sweep)

通过名字你就可以理解,标记清除分为两个阶段:标记阶段和清除阶段。

首先它会遍历堆上的所有的对象,分别对它们打上标记;然后在代码执行过程结束之后,对使用过的变量取消标记。那么没取消标记的就是没有使用过的变量,因此在清除阶段,就会把还有标记的进行整体清除,从而释放内存空间。

听起来这一切都比较完美,但是其实通过标记清除之后,还是会出现上面图中的内存碎片的问题。内存碎片多了之后,如果要新来一个较大的内存对象需要存储,会造成影响。对于通过标记清除产生的内存碎片,还是需要通过另外一种方式进行解决,因此这里就不得不提到标记整理策略(Mark-Compact)了。下面我们就来看看标记整理策略是如何帮助清除内存碎片的问题的。

标记整理(Mark-Compact)

经过标记清除策略调整之后,老生代的内存中因此产生了很多内存碎片,若不清理这些内存碎片,之后会对存储造成影响。

为了方便解决浏览器中的内存碎片问题,标记整理这个策略被提出。这个策略是在标记清除的基础上演进而来的,和标记清除来对比来看,标记整理添加了活动对象整理阶段,处理过程中会将所有的活动对象往一端靠拢,整体移动完成后,直接清理掉边界外的内存。

内存泄漏与优化

平常用 JavaScript 开发代码,内存的泄漏和优化是应该经常留意的。内存泄漏是指 JavaScript 中,已经分配堆内存地址的对象由于长时间未释放或者无法释放,造成了长期占用内存,使内存浪费,最终会导致运行的应用响应速度变慢以及最终崩溃的情况。这种就是内存泄漏,你应该在日常开发和使用浏览器过程中也遇到过,那么我们来回顾一下内存泄漏的场景:

过多的缓存未释放;

闭包太多未释放;

定时器或者回调太多未释放;

太多无效的 DOM 未释放;

全局变量太多未被发现。

如何优化?

  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。例如:
function foo() {
    // 全局变量=> window.bar
    this.bar = '默认this指向全局';
    // 没有声明变量,实际上是全局变量=>window.bar
    bar = '全局变量'; 
}
foo();
  1. 在你使用完数据后,及时解除引用(闭包中的变量,DOM 引用,定时器清除)。例如:
var someResource = getData();
setInterval(function() {
    var node = document.getElementById('Node');
    if(node) {
        node.innerHTML = JSON.stringify(someResource));
        // 定时器也没有清除,可以清除掉
    }
    // node、someResource 存储了大量数据,无法回收
}, 1000);
  1. 组织好你的代码逻辑,避免死循环等造成浏览器卡顿、崩溃的问题。例如,对于一些比较占用内存的对象提供手工释放内存的方法,请看下面代码:
var leakArray = [];
exports.clear = function () {
    leakArray = [];
}