JavaScript内存管理详解

603 阅读3分钟

JavaScript内存管理详解

JavaScript会在创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放内存,这个自动释放内存的过程称为垃圾回收。

1. 内存的生命周期

Image text

1.1. JS 的内存分配

栈内存:存放基本类型;

堆内存:存放引用类型(在栈内存中存一个基本类型值保存对象在堆内存中的地址,用于引用这个对象。)

var count = 3; 
var str = "hgtfsad"; 
var obj = {
    key: 1,
    value: 'string'
}; 
var date = new Date(); 
var ele = document.createElement('div'); 

1.2. JS 的内存使用

读写内存,也就是使用变量、函数等

var a = 10; 
console.log(a);

1.3. JS 的内存回收

JS 有自动垃圾回收机制,原理就是找出那些不再继续使用的值,然后释放其占用的内存。

局部变量:只在函数的执行过程中存在;

全局变量:生命周期直至浏览器卸载页面才会结束,也就是说全局变量不会被当成垃圾回收

var index=0;
var name="ming";
function sum() {
    var a=1;
    var b=2;
    return a+b;
}
console.log(sum());

2. 垃圾回收

2.1 引用

垃圾回收算法主要依赖于引用的概念。

在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。

var name='Bob';
var obj={
    key:1,
    name:'Alice'
};
name=obj.name; //隐式引用
var info=obj; //显示引用

2.2 引用计数垃圾收集

是最初级的垃圾回收算法,就是看一个对象是否有指向它的引用,若没有其他对象指向它了,说明该对象已经不再需要了。

var people = '';
var obj = {
    key:1,
    name: 'Alice'
}  // 引用计数为1

people = obj.name;//引用计数为2

obj = null;//引用计数为1
people = null;//计数为0

致命的问题:循环引用,相互引用依然存在,计数不为0

function f(){
    var o1 = {};
    var o2 = {};
    o1.a = o2; 
    o2.a = o1; 
    
    return "azerty";
}

f();

2.3. 标记清除算法

从根部(在JS中就是全局对象)出发定时扫描内存中的对象。凡是能从根部到达的对象,都是还需要使用的。那些无法由根部出发触及到的对象被标记为不再使用,稍后进行回收。

Image text

3. 内存泄漏

3.1 概念

内存泄漏就是由于疏忽或错误,造成程序未能释放那些已经不再使用的内存,造成内存的浪费。不断出现内存泄漏,则会造成OOM, 进程崩溃。

3.2内存泄漏识别方法

两种方式来判定当前是否有内存泄漏:

多次快照后,比较每次快照中内存的占用情况,如果呈上升趋势,那么可以认为存在内存泄漏
某次快照后,看当前内存占用的趋势图,如果走势不平稳,呈上升趋势,那么可以认为存在内存泄漏

在服务器环境中使用 Node 提供的 process.memoryUsage 方法查看内存情况

3.2 常见内存泄漏案例

案例一:不小心创建全局变量
function foo() {
    bar1 = 'some text'; 
    this.bar2 = 'some text';
}
foo();
案例二:未及时关闭定时器
var serverData = loadData();
setInterval(function() {
  var renderer = document.getElementById('renderer');
  if(renderer) {
    renderer.innerHTML = JSON.stringify(serverData);
  }
}, 5000);
案例三:闭包
var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
     if (originalThing) // 对于 'originalThing'的引用
     console.log("hi");
  };
  theThing = {
     longStr: new Array(1000000).join('*'),
     someMethod: function () {
        console.log("message");
     }
  };
};
setInterval(replaceThing, 1000);

3.3 如何避免内存泄漏

记住一个原则:不用的东西,及时归还。

减少不必要的全局变量,使用严格模式避免意外创建全局变量。
在使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
组织好逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。