3.1.4 JS堆栈池

#内存模型

JS内存空间分为栈(stack)、堆(heap)、池(一般也会归类为栈中)。 其中栈存放变量,堆存放复杂对象,池存放常量。

为了更好的搞懂栈内存与堆内存,我们可以结合以下例子与图解进行理解。

var a1 = 0; // 栈 
var a2 = 'this is string'; // 栈
var a3 = null; // 栈 
var b = { m: 20 }; // 变量b存在于栈中,{m: 20} 作为对象存在于堆内存中
var c = [1, 2, 3]; // 变量c存在于栈中,[1, 2, 3] 作为对象存在于堆内存中

作者:梁音
链接:https://juejin.im/post/5b10ba336fb9a01e66164346
1
2
3
4
5
6
7
8

#内存的生命周期

JS环境中分配的内存一般有如下生命周期:

  • 内存分配:当我们申明变量、函数、对象的时候,系统会自动为他 们分配内存
  • 内存使用:即读写内存,也就是使用变量、函数等
  • 内存回收:使用完毕,由垃圾回收机制自动回收不再使用的内存

为了便于理解,我们使用一个简单的例子来解释这个周期。

var a = 20;  // 在内存中给数值变量分配空间
alert(a + 100);  // 使用内存
var a = null; // 使用完毕之后,释放内存空间
1
2
3

#内存回收

#垃圾回收算法

  • 引用计数算法
    • 引用计数算法定义“内存不再使用”的标准很简单,就是看一个对象是否有指向它的引用。如果没有其他对象指向它了,说明该对象已经不再需了。
  • 标记清除算法
    • 现代的浏览器已经不再使用引用计数算法了。现代浏览器通用的大多是基于标记清除算法的某些改进算法,总体思想都是一致的
    • 标记清除算法将“不再使用的对象”定义为“无法达到的对象”。简单来说,就是从根部(在JS中就是全局对象)出发定时扫描内存中的对象。
    • 凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

#内存泄露

  • 不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

内存泄漏的识别方法

  • 浏览器方法

打开开发者工具,选择 Timeline 面板
在顶部的Capture字段里面勾选 Memory
点击左上角的录制按钮。
在页面上进行各种操作,模拟用户的使用情况。
一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。

如果内存占用基本平稳,接近水平,就说明不存在内存泄漏。
反之,就是内存泄漏了。

  • 命令行方法

命令行可以使用 Node 提供的 process.memoryUsage 方法。

console.log(process.memoryUsage());
// { rss: 27709440,
//  heapTotal: 5685248,
//  heapUsed: 3449392,
//  external: 8772 }
1
2
3
4
5

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义如下。

Resident Set(常驻内存)

Code Segment(代码区)

Stack(Local Variables, Pointers)

Heap(Objects, Closures)

Used Heap


rss(resident set size):所有内存占用,包括指令区和堆栈。
heapTotal:"堆"占用的内存,包括用到的和没用到的。
heapUsed:用到的堆的部分。
external: V8 引擎内部的 C++ 对象占用的内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

判断内存泄漏,以heapUsed字段为准。

#参考资料

  • http://www.ruanyifeng.com/blog/2017/04/memory-leak.html

  • https://juejin.im/post/5b10ba336fb9a01e66164346

  • https://www.jianshu.com/p/84a8fd5fa0ee