【重温基础】22.内存管理

栏目: JavaScript · 发布时间: 5年前

内容简介:本文是今日感受:优化学习方法。系列目录:

本文是 重温基础 系列文章的第二十二篇。

今日感受:优化学习方法。

系列目录:

本章节复习的是JS中的内存管理,这对于我们开发非常有帮助。

前置知识绝大多数的程序语言,他们的内存生命周期基本一致:

【重温基础】22.内存管理

  1. 分配所需使用的内存 ——( 分配内存
  2. 使用分配到的内存(读、写) ——( 使用内存
  3. 不需要时将其释放归还 ——( 释放内存

对于所有的编程语言,第二部分都是明确的。而第一和第三部分在底层语言中是明确的。

但在像 JavaScript 这些高级语言中,大部分都是隐含的,因为 JavaScript 具有自动垃圾回收机制(Garbage collected)。

因此在做 JavaScript 开发时,不需要关心内存的使用问题,所需内存分配和无用内存回收,都完全实现自动管理。

1.概述

C语言 这样的高级语言一般都有底层的内存管理接口,比如 malloc()free() 。另一方面,JavaScript创建变量(对象,字符串等)时分配内存,并且在不再使用它们时“自动”释放。 后一个过程称为 垃圾回收 。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者感觉他们可以不关心内存管理。 这是错误的。

——《MDN JavaScript 内存管理》

MDN中的介绍告诉我们,作为 JavaScript 开发者,还是需要去了解内存管理,虽然 JavaScript 已经给我们做好自动管理。

2.JavaScript内存生命周期

2.1 分配内存

在做 JavaScript 开发时,我们定义变量的时候, JavaScript 便为我们完成了内存分配:

var num = 100;      // 为数值变量分配内存
var str = 'pingan'; // 为字符串变量分配内存
var obj = {
    name : 'pingan'
};                  // 为对象变量及其包含的值分配内存
var arr = [1, null, 'hi']; // 为数组变量及其包含的值分配内存

function fun(num){
  return num + 2;
};                  // 为函数(可调用的对象)分配内存

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

另外,通过调用函数,也会分配内存:

// 类型1. 分配对象内存
var date = new Date();   // 分配一个Date对象
var elem = document.createElement('div'); // 分配一个DOM元素

// 类型2. 分配新变量或者新对象
var str1 = "pingan";
var str2 = str1.substr(0, 3); // str2 是一个新的字符串

var arr1 = ["hi", "pingan"];
var arr2 = ["hi", "leo"];
var arr3 = arr1.concat(arr2); // arr3 是一个新的数组(arr1和arr2连接的结果)

2.2 使用内存

使用内存的过程实际上是对分配的内存进行读取与写入的操作。

通常表现就是使用定义的值。

读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。

var num = 1;
num ++; // 使用已经定义的变量,做递增操作

2.3 释放内存

当我们前面定义好的变量或函数(分配的内存)已经不需要使用的时候,便需要释放掉这些内存。这也是内存管理中最难的任务,因为我们不知道什么时候这些内存不使用。

很好的是,在高级语言解释器中,已经嵌入“ 垃圾回收器 ”,用来 跟踪内存的分配和使用 ,以便在内存不使用时自动释放(这并不是百分百跟踪到,只是个近似过程)。

3.垃圾回收机制

就像前面提到的,“ 垃圾回收器 ”只能解决一般情况,接下来我们需要了解主要的垃圾回收算法和它们局限性。

3.1 引用

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

即在内存管理环境中,一个对象如果有权限访问另一个对象,不论显式还是隐式,称为一个对象 引用 另一个对象。

例如:一个JS对象具有对它 原型的引用 (隐式引用)和对它 属性的引用 (显式引用)。

注意:这里的对象,不仅包含JS对象,也包含函数作用域(或全局词法作用域)。

3.2 引用计数垃圾收集

这个算法,把“ 对象是否不再需要 ”定义为:当一个对象没有被其他对象所引用的时候,回收该对象。这是最初级的垃圾收集算法。

var obj = {
    leo : {
        age : 18
    };
};

这里创建2个对象,一个作为 leo 的属性被引用,另一个被分配给变量 obj

// 省略上面的代码
/*
我们将前面的
    {
        leo : {
            age : 18
        };
    };
称为“这个对象”
*/
var obj2 = obj;  // obj2变量是第二个对“这个对象”的引用
obj = 'pingan';  // 将“这个对象”的原始是引用obj换成obj2

var leo2 = obj2.leo;  // 引用“这个对象”的leo属性

可以看出,现在的“这个对象”已经有2个引用,一个是 obj2 ,另一个是 leo2

obj2 = 'hi'; 
// 将obj2变成零引用,因此,obj2可以被垃圾回收
// 但是它的属性leo还在被leo2对象引用,所以还不能回收

leo2 = null;
// 将leo变成零引用,这样obj2和leo2都可以被垃圾回收

这个算法有个限制:

无法处理 循环引用 。即两个对象创建时相互引用形成一个循环。

function fun(){
    var obj1 = {}, obj2 = {};
    obj1.leo = obj2;  // obj1引用obj2
    obj2.leo = obj1;  // obj2引用obj1
    return 'hi pingan';
}
fun();

【重温基础】22.内存管理

可以看出,它们被调用之后,会离开函数作用域,已经没有用了可以被回收,然而 引用计数算法 考虑到它们之间相互至少引用一次,所以它们不会被回收。

实际案例:

在IE6,7中,使用引用计数方式对DOM对象进行垃圾回收,常常造成对象被循环引用导致内存泄露:

var obj;
window.onload = function(){
    obj = document.getElementById('myId');
    obj.leo = obj;
    obj.data = new Array(100000).join('');
};

可以看出,DOM元素 obj 中的 leo 属性引用了自己 obj ,造成循环引用,若该属性( leo )没有移除或设置为 null ,垃圾回收器总是且至少有一个引用,并一直占用内存,即使从DOM树删除,如果这个DOM元素含大量数据(如 data 属性)则会导致占用内存永远无法释放,出现内存泄露。

3.3 标记清除算法

这个算法,将“ 对象是否不再需要 ”定义为:对象是否可以获得。

【重温基础】22.内存管理

标记清除算法,是假定设置一个根对象(root),在JS中是全局对象。垃圾回收器定时找所有从根开始引用的对象,然后再找这些对象引用的对象...直到找到所有 可以获得的对象搜集所有不能获得的对象

它比 引用计数垃圾收集 更好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。

循环引用不再是问题:

function fun(){
    var obj1 = {}, obj2 = {};
    obj1.leo = obj2;  // obj1引用obj2
    obj2.leo = obj1;  // obj2引用obj1
    return 'hi pingan';
}
fun();

还是这个代码,可以看出,使用 标记清除算法 来看,函数调用之后,两个对象无法从全局对象获取,因此将被回收。相同的,下面案例,一旦 obj 和其事件处理无法从根获取到,他们将会被垃圾回收器回收。

var obj;
window.onload = function(){
    obj = document.getElementById('myId');
    obj.leo = obj;
    obj.data = new Array(100000).join('');
};

注意:那些无法从根对象查询到的对象都将被清除。

3.4 个人小结

在日常开发中,应该注意及时切断需要回收对象与根的联系,虽然标记清除算法已经足够强壮,就像下面代码:

var obj,ele=document.getElementById('myId');
obj.div = document.createElement('div');
ele.appendChild(obj.div);
// 删除DOM元素
ele.removeChild(obj.div);

如果我们只是做小型项目开发,JS用的比较少的话,内存管理可以不用太在意,但是如果是大项目(SPA,服务器或桌面应用),那就需要考虑好内存管理问题了。

4.内存泄露(Memory Leak)

4.1 内存泄露概念

在计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。 ——维基百科

其实简单理解: 一些不再使用的内存无法被释放

当内存占用越来越多,不仅影响系统性能,严重的还会导致进程奔溃。

4.2 内存泄露案例

  1. 全局变量

未定义的变量,会被定义到全局,当页面关闭才会销毁,这样就造成内存泄露。如下:

function fun(){
    name = 'pingan';
};
  1. 未销毁的定时器和回调函数

如果这里举一个定时器的案例,如果定时器没有回收,则不仅整个定时器无法被内存回收,定时器函数的依赖也无法回收:

var data = {};
setInterval(function(){
    var render = document.getElementById('myId');
    if(render){
        render.innderHTML = JSON.stringify(data);
    }
}, 1000);
  1. 闭包
var str = null;
var fun = function(){
    var str2 = str;
    var unused = function(){
        if(str2) console.log('is unused');
    };
    str = {
        my_str = new Array(100000).join('--');
        my_fun = function(){
            console.log('is my_fun');
        };
    };
};
setInterval(fun, 1000);

定时器中每次调用 funstr 都会获得一个包含巨大的数组和一个对于新闭包 my_fun 的对象,并且 unused 是一个引用了 str2 的闭包。

整个案例中,闭包之间共享作用域,尽管 unused 可能一直没有调用,但 my_fun 可能被调用,就会导致内存无法回收,内存增长导致泄露。

  1. DOM引用

当我们把DOM的引用保存在一个数组或Map中,即使移除了元素,但仍然有引用,导致无法回收内存。例如:

var ele = {
    img : document.getElementById('my_img')
};
function fun(){
    ele.img.src = "http://www.baidu.com/1.png";
};
function foo(){
    document.body.removeChild(document.getElementById('my_img'));
};

即使 foo 方法将 my_img 元素移除,但 fun 仍有引用,无法回收。

4.3 内存泄露识别方法

  1. 浏览器

通过Chrome浏览器查看内存占用:

【重温基础】22.内存管理

步骤如下:

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

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

【重温基础】22.内存管理

反之,就是内存泄漏了。

【重温基础】22.内存管理

  1. 命令行

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

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

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

【重温基础】22.内存管理

rss(resident set size)
heapTotal
heapUsed
external

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


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Amazon Web Services

Programming Amazon Web Services

James Murty / O'Reilly Media / 2008-3-25 / USD 49.99

Building on the success of its storefront and fulfillment services, Amazon now allows businesses to "rent" computing power, data storage and bandwidth on its vast network platform. This book demonstra......一起来看看 《Programming Amazon Web Services》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具