JavaScript内存管理
栏目: JavaScript · 发布时间: 5年前
内容简介:底层语言,如C,有底层内存管理函数malloc()和free();JavaScript创建的对象不再使用的时候将会自动释放的过程称为垃圾回收,这种回收机制带来了一些问题:开发者们可以决定不关心内存管理。JavaScript 在定义变量时就完成了内存分配。有些函数调用结果是分配对象内存
底层语言,如C,有底层内存管理函数malloc()和free();JavaScript创建的对象不再使用的时候将会自动释放的过程称为垃圾回收,这种回收机制带来了一些问题:开发者们可以决定不关心内存管理。
一、内存的生命周期
JavaScript 的内存分配
1.值的初始化
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); 复制代码
2.通过函数调用分配内存
有些函数调用结果是分配对象内存
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 的结果 复制代码
3.使用值使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。
4.当内存不再需要使用时释放详情请见垃圾回收机制
垃圾回收机制
1.引用在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个Javascript对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。
在这里,“对象”的概念不仅特指 JavaScript 对象,还包括函数作用域(或者全局词法作用域)。
2.引用数垃圾回收这是最简单的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。(相关阅读: 阮一峰:JavaScript 内存泄漏教程 )
var o = { a: { b:2 } }; // 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量o // 很显然,没有一个可以被垃圾收集 var o2 = o; // o2变量是第二个对“这个对象”的引用 o = 1; // 现在,“这个对象”的原始引用o被o2替换了 var oa = o2.a; // 引用“这个对象”的a属性 // 现在,“这个对象”有两个引用了,一个是o2,一个是oa o2 = "yo"; // 最初的对象现在已经是零引用了 // 他可以被垃圾回收了 // 然而它的属性a的对象还在被oa引用,所以还不能回收 oa = null; // a属性的那个对象现在也是零引用了 // 它可以被垃圾回收了 复制代码
3.标记清除法这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和所有不能获得的对象。
这个算法比前一个要好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。
二、内存泄漏问题
常见的内存泄漏类型
1.全局变量JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。在浏览器中,全局对象是 window 。
function foo(arg) { bar = "这是一个局部变量"; } //实际情况是 function foo(arg) { window.bar = "这实际是一个全局变量"; } 复制代码
或者是
function foo() { this.variable = "potential accidental global"; } // Foo 调用自己,this 指向了全局对象(window) // 而不是 undefined foo(); 复制代码
注意事项:尽管我们讨论了一些意外的全局变量,但是仍有一些明确的全局变量产生的垃圾。它们被定义为不可回收(除非定义为空或重新分配)。尤其当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。与全局变量相关的增加内存消耗的一个主因是缓存,缓存内容无法被回收。
相关阅读: 命名空间-极客学院 立即执行函数IIFE-MDN 立即执行函数IIFE-伯乐在线
2:计时器或回调函数
var someResource = getData(); setInterval(function() { var node = document.getElementById('Node'); if(node) { // 处理 node 和 someResource node.innerHTML = JSON.stringify(someResource)); } }, 1000); 复制代码
此例说明了什么:与节点或数据关联的计时器不再需要,node 对象可以删除,整个回调函数也不需要了。可是,计时器回调函数仍然没被回收(计时器停止才会被回收)。同时,someResource 如果存储了大量的数据,也是无法被回收的。
观察者代码示例:
var element = document.getElementById('button'); function onClick(event) { element.innerHTML = 'text'; } element.addEventListener('click', onClick); 复制代码
老版本的 IE 是无法检测 DOM 节点与 JavaScript 代码之间的循环引用,会导致内存泄漏。如今,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法,已经可以正确检测和处理循环引用了。换言之,回收节点内存时,不必非要调用 removeEventListener 了。
3:脱离 DOM 的引用
var elements = { button: document.getElementById('button'), image: document.getElementById('image'), text: document.getElementById('text') }; function doStuff() { image.src = 'http://some.url/image'; button.click(); console.log(text.innerHTML); // 更多逻辑 } function removeButton() { // 按钮是 body 的后代元素 document.body.removeChild(document.getElementById('button')); // 此时,仍旧存在一个全局的 #button 的引用 // elements 字典。button 元素仍旧在内存中,不能被 GC 回收。 } 复制代码
有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时, 需要把两个引用都清除 。
4.闭包
相关阅读:闭包-MDN
Chrome 内存剖析工具
Chrome 任务管理器
下面两列可以告诉您与页面的内存使用有关的不同信息: 1.Memory 列表示原生内存。DOM 节点存储在原生内存中。 如果此值正在增大,则说明正在创建 DOM 节点。 2.JavaScript Memory 列表示 JS 堆。此列包含两个值。 您感兴趣的值是实时数字(括号中的数字)。 实时数字表示您的页面上的可到达对象正在使用的内存量。 如果此数字在增大,要么是正在创建新对象,要么是现有对象正在增长。
Performance工具
chrome浏览器在57版本后Timeline工具更名为Performance。相关阅读: Performance-chorme官方文档
Performance 可以检测代码中不需要的内存。在此截图中,我们可以看到潜在的泄漏对象稳定的增长,数据采集期间内存的变化为[102-216M],出现明显的内存泄漏,从图中的增量来看,js的内存泄漏最为明显也是最主要的。
Profiles工具
Profiles工具主要是用来监控和查找浏览器内存问题的工具。相关阅读: Profiles-chorme官方文档
Profiles工具分为三种类型: 1.Take heap snapshot 保存当前内存快照 2.Record allocation profile 记录一段时间的内存分配和使用情况 3.Record allocation timeline 以时间线记录内存的分配和使用情况
实例:使用 Chrome 发现内存泄漏
以chrome文档中的代码为例:
var x = []; function createSomeNodes() { var div, i = 100, frag = document.createDocumentFragment(); for (;i > 0; i--) { div = document.createElement("div"); div.appendChild(document.createTextNode(i + " - "+ new Date().toTimeString())); frag.appendChild(div); } document.getElementById("nodes").appendChild(frag); } function grow() { x.push(new Array(1000000).join('x')); createSomeNodes(); setTimeout(grow,1000); } 复制代码
当 grow 执行的时候,开始创建 div 节点并插入到 DOM 中,并且给全局变量分配一个巨大的数组。通过以上提到的 工具 可以检测到内存稳定上升。
找出周期性增长的内存Performance 工具擅长做这些。在 Chrome 中打开例子,打开 Dev Tools ,切换到 Performance,勾选 memory 并点击记录按钮,然后点击页面上的 The Button
按钮。过一阵停止记录看结果:
两种迹象显示出现了内存泄漏,图中的 Nodes(绿线)和 JS heap(蓝线)。Nodes 稳定增长,并未下降,这是个显著的信号。
JS heap 的内存占用也是稳定增长。由于垃圾收集器的影响,并不那么容易发现。图中显示内存占用忽涨忽跌,实际上每一次下跌之后,JS heap 的大小都比原先大了。换言之,尽管垃圾收集器不断的收集内存,内存还是周期性的泄漏了。
确定存在内存泄漏之后,我们找找根源所在。待补充。。。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Go 语言内存管理(二):Go 内存管理
- Objective-C的内存管理(1)——内存管理概述
- [译] 图解 Go 内存管理与内存清理
- 图解 Go 内存管理器的内存分配策略
- Go:内存管理分配
- Redis内存管理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。