【JS】内存管理

内存生命周期

  • 不管什么程序语言,内存生命周期基本是一致的:
1. 分配你所需的内存
2. 使用分配到的内存(比如进行读写操作)
3. 不需要时将其归还、释放

所有语言第二部分都是明确的。第一和第三部分在底层语言中是明确的,但在像JavaScript这些高级语言中,大部分都是隐含的。
JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。这个“自动”是混乱的根源,并让JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。

内存分配

  • 为了不让程序员费心分配内存,JavaScript 在定义变量时就完成了内存分配。
var n = 123; // 给数值变量分配内存
var s = "azerty"; // 给字符串分配内存

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 元素
  • 有些方法分配新变量或者新对象:
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一个新的字符串
// 因为字符串是不变量,
// JavaScript 可能决定不分配内存,
// 只是存储了 [0-3] 的范围。

var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新数组有四个元素,是 a 连接 a2 的结果

使用值

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

当内存不再需要使用时释放

高级语言解释器嵌入了“垃圾回收器”,它的主要工作是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。这只能是一个近似的过程,因为要知道是否仍然需要某块内存是无法判定的(无法通过某种算法解决)。

  • 大多数内存管理的问题都在这个阶段。
  • 在这里最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”。
  • 它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它。

内存泄漏

内存泄露概念

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

  • 程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存
  • 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。
  • 否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。
  • 不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

内存泄露案例

全局变量:

  • 案例 1 : 在 JS 中处理未被声明的变量
function fun() {
    text = "文本";
}
上述范例中的 text 时, 会把 text , 定义到全局对象中, 在浏览器中就是 window 上. 
在页面中的全局变量, 只有当页面被关闭后才会被销毁. 所以这种写法就会造成内存泄露, 
当然在这个例子中泄露的只是一个简单的字符串, 但是在实际的代码中, 往往情况会更加糟糕.
  • 案例 2:意外创建全局变量
function fun() {
    this.text = "文本";
}
// fun 被调用时, this 指向全局变量(window)
fun();
在这种情况下调用 foo, this被指向了全局变量 window, 意外的创建了全局变量.
  • 总结:
我们谈到了一些意外情况下定义的全局变量, 代码中也有一些我们明确定义的全局变量. 
如果使用这些全局变量用来暂存大量的数据, 记得在使用后, 对其重新赋值为 null.

未销毁的定时器和回调函数

  • 在很多库中, 如果使用了观察着模式, 都会提供回调方法, 来调用一些回调函数. 要记得回收这些回调函数.
var serverData = loadData();
setInterval(function() {
    var renderer = document.getElementById('renderer');
    if(renderer) {
        renderer.innerHTML = JSON.stringify(serverData);
    }
}, 5000); // 每 5 秒调用一次
如果后续 renderer 元素被移除, 整个定时器实际上没有任何作用. 
但如果你没有回收定时器, 整个定时器依然有效, 
不但定时器无法被内存回收, 定时器函数中的依赖也无法回收. 
在这个案例中的 serverData 也无法被回收.

闭包

  • 闭包:一个内部函数, 有权访问包含其的外部函数中的变量
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);
这段代码, 每次调用 replaceThing 时:
theThing 获得了包含一个巨大的数组和一个对于新闭包 someMethod 的对象. 
同时 unused 是一个引用了 originalThing 的闭包。

这个范例的关键在于, 闭包之间是共享作用域的。
尽管 unused 可能一直没有被调用, 但是someMethod 可能会被调用, 就会导致内存无法对其进行回收.
当这段代码被反复执行时, 内存会持续增长.

DOM 引用

  • 很多时候, 我们对 Dom 的操作, 会把 Dom 的引用保存在一个数组或者 Map 中
var elements = {
    image: document.getElementById('image')
};
function doStuff() {
    elements.image.src = 'http://example.com/image_name.png';
}
function removeImage() {
    document.body.removeChild(document.getElementById('image'));
    // 这个时候我们对于 #image 仍然有一个引用, Image 元素, 仍然无法被内存回收. 
}
上述案例中, 即使我们对于 image 元素进行了移除, 但是仍然有对 image 元素的引用, 依然无法对齐进行内存回收.

内存泄漏识别

  • Chrome 浏览器查看内存占用,按照以下步骤操作
1. 打开开发者工具,选择 Timeline 面板
2. 在顶部的Capture字段里面勾选 Memory
3. 点击左上角的录制按钮。
4. 在页面上进行各种操作,模拟用户的使用情况。
5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况。
  • 老版的图
    在这里插入图片描述
  • 新版图如下:

在这里插入图片描述

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

在这里插入图片描述

  • 反之,就是内存泄漏了。

在这里插入图片描述

  • 命令行可以使用 Node 提供的process.memoryUsage方法。
console.log(process.memoryUsage());
// { rss: 27709440,
//  heapTotal: 5685248,
//  heapUsed: 3449392,
//  external: 8772 }
  • 判断内存泄漏,以heapUsed字段为准。
rss(resident set size):所有内存占用,包括指令区和堆栈。
heapTotal:"堆"占用的内存,包括用到的和没用到的。
heapUsed:用到的堆的部分。
external: V8 引擎内部的 C++ 对象占用的内存。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一颗不甘坠落的流星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值