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

因为自动垃圾回收机制的存在,让大多Javascript开发者感觉他们可以不关心内存管理,所以会在一些情况下导致内存泄漏。

内存生命周期

JS 环境中分配的内存有如下声明周期:

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

JS 的内存分配

为了不让程序员费心分配内存,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 的结果

JS 的内存使用

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

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

var a = 10; // 分配内存
console.log(a); // 对内存的使用

JS 的内存回收

JS 有自动垃圾回收机制,那么这个自动垃圾回收机制的原理是什么呢?

其实很简单,就是找出那些不再继续使用的值,然后释放其占用的内存。

大多数内存管理的问题都在这个阶段。

在这里最艰难的任务是找到不再需要使用的变量。

不再需要使用的变量也就是生命周期结束的变量,是局部变量,局部变量只在函数的执行过程中存在,

当函数运行结束,没有其他引用(闭包),那么该变量会被标记回收。

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

因为自动垃圾回收机制的存在,开发人员可以不关心也不注意内存释放的有关问题,但对无用内存的释放这件事是客观存在的。

不幸的是,即使不考虑垃圾回收对性能的影响,目前最新的垃圾回收算法,也无法智能回收所有的极端情况。

一、什么是内存泄漏

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

本质上讲,内存泄漏就是由于疏忽或错误造成程序未能释放那些不再使用的内存,照成内存的浪费。

简单地说就是申请了一块内存空间,使用完毕后没有释放掉。它的一般表现方式是程序运行时间越长,占用内存越多,最终用尽全部内存,整个系统崩溃。由程序申请的一块内存,且没有任何一个指针指向它,那么这块内存就泄露了。

参考链接:https://www.toutiao.com/a6709023162064962051/?tt_from=weixin&utm_campaign=client_share&wxshare_count=1&timestamp=1567217731&app=news_article&utm_source=weixin&utm_medium=toutiao_ios&req_id=20190831101530010152039098792EF53&group_id=6709023162064962051

二、内存泄漏的识别办法

经验法则是,如果连续5次垃圾回收之后,内存占用一次比一次大,就有内存泄漏。

这就要求实时查看内存的占用情况。

三、在Chrome浏览器中,我们怎么查看内存占用情况?

  1. 打开开发者工具,选择 Performance 面板
  2. 在顶部勾选 Memory
  3. 点击左上角的 record 按钮
  4. 在页面上进行各种操作,模拟用户的使用情况
  5. 一段时间后,点击对话框的 stop 按钮,面板上就会显示这段时间的内存占用情况(如下图)

1.

2.

3.

4.

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

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

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

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

process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。

该对象包含四个字段,单位是字节,含义如下:

  • rss(resident set size):所有内存占用,包括指令区和堆栈。
  • heapTotal:"堆"占用的内存,包括用到的和没用到的。
  • heapUsed:用到的堆的部分。
  • external: V8 引擎内部的 C++ 对象占用的内存。

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

常见的内存泄露案例:

1.意外的全局变量

function foo() {bar1 = 'some text'; // 没有声明变量 实际上是全局变量 => window.bar1this.bar2 = 'some text' // 全局变量 => window.bar2
}
foo();

在这个例子中,意外的创建了两个全局变量 bar1 和 bar2

2.被遗忘的定时器和回调函数

在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数。

要记得回收这些回调函数。举一个 setInterval的例子:

var serverData = loadData();
setInterval(function() {var renderer = document.getElementById('renderer');if(renderer) {renderer.innerHTML = JSON.stringify(serverData);}
}, 5000); // 每 5 秒调用一次

如果后续 renderer 元素被移除,整个定时器实际上没有任何作用。

但如果你没有回收定时器,整个定时器依然有效, 不但定时器无法被内存回收,

定时器函数中的依赖也无法回收。在这个案例中的 serverData 也无法被回收。

3.闭包

在 JS 开发中,我们会经常用到闭包,一个内部函数,有权访问包含其的外部函数中的变量。

下面这种情况下,闭包也会造成内存泄露:

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 可能会被调用,就会导致无法对其内存进行回收。

当这段代码被反复执行时,内存会持续增长。

4.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 元素的引用,依然无法对齐进行内存回收。

另外需要注意的一个点是,对于一个 Dom 树的叶子节点的引用。

举个例子: 如果我们引用了一个表格中的td元素,一旦在 Dom 中删除了整个表格,我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素。

但是事实上,这个 td 元素是整个表格的一个子元素,并保留对于其父元素的引用。

这就会导致对于整个表格,都无法进行内存回收。所以我们要小心处理对于 Dom 元素的引用。

四、如何避免内存泄漏

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

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

什么是内存泄漏?Chrome浏览器中怎么查看内存占用情况?相关推荐

  1. notepad++ 编写html代码快捷键切换到浏览器查看,notepad++在chrome浏览器中打开查看网页效果...

    notepad++在chrome浏览器中打开查看网页效果,操作设置:运行--在chrome浏览器中打开,这是notepad默认的设置方式 但是notepad++如果没有配置chrome浏览器打开,可以 ...

  2. linux怎么看系统内存多大内存频率,linux 系统管理中的查看内存插槽数最大容量和频率...

    Linux 查看内存的插槽数,已经使用多少插槽.每条内存多大,已使用内存多大 1.dmidecode|grep -P -A5 "Memory\s+Device"|grep Size ...

  3. 如何在Google Chrome浏览器中启动JavaScript调试器?

    使用Google Chrome浏览器时,我想调试一些JavaScript代码. 我怎样才能做到这一点? #1楼 在Chrome浏览器中按F12功能键以启动JavaScript调试器,然后单击" ...

  4. 【谷歌】Google Chrome 浏览器中 font-size 12px 没有效果

    Google Chrome 浏览器中 font-size < 12px 没有效果 解决方法: *{-webkit-text-size-adjust: none;} 此功能立竿见影,目的是去掉CH ...

  5. 如何在Chrome浏览器中创建账户?

    chrome浏览器是一款非常特殊的浏览器,不仅有非常多的用户喜欢使用这款软件,有很多开发者也非常喜欢使用这款软件,在使用浏览器的时候创建账号是一种最简单的方法,其实操作起来非常的简单.方便,但不是所有 ...

  6. java实现浏览器ui中的收藏夹_谷歌改进Google Chrome浏览器中的PDF浏览器 带来全新UI...

    最近,谷歌一直在不断地改进在Chrome浏览器的PDF浏览器.例如,它开发了一个选项,以查看PDF的两页视图,并让你保存PDF表格.但这并不足够,由于目前的用户界面信息贫乏容易引发误解和困扰,谷歌正在 ...

  7. Chrome 浏览器中,使用 Shift + Esc 打开任务管理器 / 浏览器进程管理 结束进程

    如上,偶然发现的,记录一下: Chrome 浏览器中,使用 Shift + Esc 打开任务管理器 而对于 Edge 也是同理

  8. 在Chrome浏览器中保存的密码有多安全?

    本文由 伯乐在线 - 黄利民 翻译.未经许可,禁止转载! 英文出处:howtogeek.欢迎加入翻译组. [2013-08-09 更新]:最近又开始讨论"Chrome浏览器明文保存密码这个话 ...

  9. 查看 chrome 浏览器中的 Headers

    查看 chrome 浏览器中的 Headers, Response 信息:

最新文章

  1. Burpsuite学习(4)
  2. 无线网络实体图生成工具airgraph-ng
  3. sersync + rsync 实现文件的实时同步
  4. 从0到1走进 Kaggle
  5. JavaFX移动应用程序最佳实践,第2部分
  6. box-shadow IE8兼容处理
  7. python-操作数据库的练习
  8. Mr.J--初识Ajax
  9. Linux下搭建mysql主从服务器
  10. kubenetes 1.4的Master节点克隆并改造为node节点以及node查询问题解决
  11. Android中注册一个 BroadcastReceiver的代码
  12. ESP8266连接手机
  13. 第一节计算机课要教什么作用,信息技术第一节课要求
  14. 网卡构造:MAC与PHY的关系,GMAC介绍
  15. 跨境电商的痛点有哪些?
  16. 【pytorch】torchvision.transforms 图像的变换详解;图像的预处理;数据增强
  17. 深入理解YII2.0
  18. Java学习笔记-组件及处理事件(二)(ActionEvent,DocumentEvent,ItemEvent)
  19. Pintech品致柔性探头柔性电流传感器的技术特点
  20. Java中this关键字和super关键字用法

热门文章

  1. laravel5.5实现支付宝支付
  2. LeetCode 790. 多米诺和托米诺平铺
  3. 【Android】蓝牙开发——BLE(低功耗蓝牙)(附完整Demo)
  4. Hongyang 生命不息,奋斗不止,万事起于忽微,量变引起质变 目录视图 摘要视图 订阅 Android 属性动画(Property Animation) 完全解析 (上)
  5. 【Java实战】微信Native扫码支付(主扫)开发详解
  6. 【数据结构基础】之链表介绍,生动形象,通俗易懂,算法入门必看
  7. 帝国CMS仿《5857壁纸站》整站源码/帝国CMS图片网站模板/漂亮图片壁纸站源码
  8. 支付宝APP支付 ALIN10146-自查方案
  9. Codasip宣布任命Axel Strotbek为新任董事会主席
  10. JavaScript高级 笔记