javascript中的内存管理

简介

在c语言中,我们需要手动分配和释放对象的内存,但是在java中,所有的内存管理都交给了java虚拟机,程序员不需要在手动进程内存的分配和释放,大大的减少了程序编写的难度。

同样的,在javascript中,内存管理也是自动进行的,虽然有自动的内存管理措施,但是这并不意味着程序员就不需要关心内存管理了。

本文将会进行详细的介绍javascript中的内存管理策略。

内存生命周期

对于任何程序来说,内存的生命周期通常都是一样的。

可以分为三步:

  1. 在可用空间分配内存
  2. 使用该内存空间
  3. 在使用完毕之后,释放该内存空间

所有的程序都需要手动执行第二步,对于javascript来说,第1,3两步是隐式实现的。

我们看下javascript中分配内存空间的例子。

通过初始化分配内存空间:

var n = 123; // 为数字分配内存
var s = 'azerty'; // 为String分配内存 var o = {
a: 1,
b: null
}; // 为对象分配内存 // 为数组分配内存
var a = [1, null, 'abra']; function f(a) {
return a + 2;
} // 为函数分配内存

通过函数调用分配内存空间:

var d = new Date(); // 通过new分配date对象

var e = document.createElement('div'); // 分配一个DOM对象

var s = 'azerty';
var s2 = s.substr(0, 3); // 因为js中字符串是不可变的,所以substr的操作将会创建新的字符串 var a = ['ouais ouais', 'nan nan'];
var a2 = ['generation', 'nan nan'];
var a3 = a.concat(a2);
// 同样的,concat操作也会创建新的字符串

释放空间最难的部分就是需要判断空间什么时候不再被使用。在javascript中这个操作是由GC垃圾回收器来执行的。

垃圾回收器的作用就是在对象不再被使用的时候进行回收。

JS中的垃圾回收器

判断一个对象是否可以被回收的一个非常重要的标准就是引用。

如果一个对象被另外一个对象所引用,那么这个对象肯定是不能够被回收的。

引用计数垃圾回收算法

引用计数垃圾回收算法是一种比较简单和简洁的垃圾回收算法。他把对象是否能够被回收转换成了对象是否仍然被其他对象所引用。

如果对象没有被引用,那么这个对象就是可以被垃圾回收的。

我们举一个引用计数的例子:

var x = {
a: {
b: 2
}
};
//我们创建了两个对象,a对象和a外面用大括号创建的对象。
// 我们将大括号创建的对象引用赋值给了x变量,所以x拥有大括号创建对象的引用,该对象不能够被回收。
// 同时,因为a对象是创建在大括号对象内部的,所以大括号对象默认拥有a对象的引用
// 因为两个对象都有引用,所以都不能够被垃圾回收 var y = x; //我们将x赋值给y,大括号对象现在拥有两个引用 x = 1; // 我们将1赋值给x,这样只有y引用了大括号的对象 var z = y.a; // 将y中的a对象引用赋值给z,a对象拥有两个引用 y = 'flydean'; // 重新赋值给y,大括号对象的引用数为0,大括号对象可以被回收了,但是因为其内部的a对象还有一个z在被引用
// 所以暂时不能被回收 z = null; // z引用也被重新赋值,a对象的引用数为0,两个对象都可以被回收了

引用计数的一个缺点就是可能会出现循环引用的情况。

考虑下面的一个例子:

function f() {
var x = {};
var y = {};
x.a = y; // x references y
y.a = x; // y references x return 'flydean';
} f();

在上面的例子中,x中的a属性引用了y。而y中的a属性又引用了x。

从而导致循环引用的情况,最终导致内存泄露。

在实际的应用中,IE6 和IE7 对DOM对象使用的就是引用计数的垃圾回收算法,所以可能会出现内存泄露的情况。

var div;
window.onload = function() {
div = document.getElementById('myDivElement');
div.circularReference = div;
div.lotsOfData = new Array(10000).join('*');
};

上面的例子中,DOM中的myDivElement元素使用circularReference引用了他本身,如果在引用计数的情况下,myDivElement是不会被回收的。

当myDivElement中包含了大量的数据的时候,即使myDivElement从DOM tree中删除了,myDivElement也不会被垃圾回收,从而导致内存泄露。

Mark-and-sweep回收算法

讲到这里,大家是不是觉得JS的垃圾回收算法和java中的很类似,java中也有引用计数和mark-and-sweep清除算法。

这种回收算法的判断标准是对象不可达。

在javascript中,通过扫描root对象(JS中的root对象那些全局对象),然后找到这些root对象的引用对象,然后再找到这些被引用对象的引用对象,一层一层的往后查找。

最后垃圾回收器会找到所有的可达的对象和不可达的对象。

使用不可达来标记不再被使用的对象可以有效的解决引用计数法中出现的循环引用的问题。

事实上,现在基本上所有的现代浏览器都支持Mark-and-sweep回收算法。

调试内存问题

如果发送了内存泄露,我们该怎么调试和发现这个问题呢?

在nodejs中我们可以添加--inspect,然后借助Chrome Debugger来完成这个工作:

node --expose-gc --inspect index.js

上面的代码将会开启nodejs的调试功能。

我们看下输出结果:

Debugger listening on ws://127.0.0.1:9229/88c23ae3-9081-41cd-98b0-d0f7ebceab5a
For help, see: https://nodejs.org/en/docs/inspector

结果告诉了我们两件事情,第一件事情就是debugger监听的端口。默认情况下将会开启127.0.0.1的9229端口。并且分配了一个唯一的UUID以供区分。

第二件事情就是告诉我们nodejs使用的调试器是Inspector。

使用Chrome devTools进行调试的前提是我们已经开启了 --inspect模式。

在chrome中输入chrome://inspect:

我们可看到chrome inspect的界面,如果你本地已经有开启inspect的nodejs程序的话,在Remote Target中就可以直接看到。

选中你要调试的target,点击inspect,即可开启Chrome devTools调试工具:

你可以对程序进行profile,也可以进行调试。

闭包Closures中的内存泄露

所谓闭包就是指函数中的函数,内部函数可以访问外部函数的参数或者变量,从而导致外部函数内部变量的引用。

我们看一个简单闭包的例子:

 function parentFunction(paramA)
{
var a = paramA;
function childFunction()
{
return a + 2;
}
return childFunction();
}

上面的例子中,childFunction引用了parentFunction的变量a。只要childFunction还在被使用,a就无法被释放,从而导致parentFunction无法被垃圾回收。事实上Closure默认就包含了对父function的引用。

我们看下面的例子:

 <html>
<body>
<script type="text/javascript">
document.write("Program to illustrate memory leak via closure");
window.onload=function outerFunction(){
var obj = document.getElementById("element");
obj.onclick=function innerFunction(){
alert("Hi! I will leak");
};
obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
// This is used to make the leak significant
};
</script>
<button id="element">Click Me</button>
</body>
</html>

上面的例子中,obj引用了 DOM 对象element,而element的onclick是outerFunction的内部函数,从而导致了对外部函数的引用,从而引用了obj。

这样最终导致循环引用,造成内存泄露。

怎么解决这个问题呢?

一个简单的办法就是在使用完obj之后,将其赋值为null,从而中断循环引用的关系:

 <html>
<body>
<script type="text/javascript">
document.write("Avoiding memory leak via closure by breaking the circular
reference");
window.onload=function outerFunction(){
var obj = document.getElementById("element");
obj.onclick=function innerFunction()
{
alert("Hi! I have avoided the leak");
// Some logic here
};
obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));
obj = null; //This breaks the circular reference
};
</script>
<button id="element">"Click Here"</button>
</body>
</html>

还有一种很简洁的办法就是不要使用闭包,将其分成两个独立的函数:

 <html>
<head>
<script type="text/javascript">
document.write("Avoid leaks by avoiding closures!");
window.onload=function()
{
var obj = document.getElementById("element");
obj.onclick = doesNotLeak;
}
function doesNotLeak()
{
//Your Logic here
alert("Hi! I have avoided the leak");
}
</script>
</head>
<body>
<button id="element">"Click Here"</button>
</body>
</html>

本文作者:flydean程序那些事

本文链接:http://www.flydean.com/js-memory-management/

本文来源:flydean的博客

欢迎关注我的公众号:「程序那些事」最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!

javascript中的内存管理的更多相关文章

  1. javascript中的内存管理和垃圾回收

    前面的话 不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存:然后,使用分配到的内存:最后,释放其内存.而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要使用垃圾回收机制.本文将 ...

  2. 浅谈JavaScript中的内存管理

    一门语言的内存存储方式是我们学习他必须要了解的,接下来让我浅谈一下自己对他的认识. 首先说,JavaScript中的变量包含两种两种类型: 1)值类型或基本类型:undefined.null.numb ...

  3. JavaScript 中的内存泄漏

    JavaScript 中的内存泄漏 JavaScript 是一种垃圾收集式语言,这就是说,内存是根据对象的创建分配给该对象的,并会在没有对该对象的引用时由浏览器收回.JavaScript 的垃圾收集机 ...

  4. JavaScript中的内存泄漏以及如何处理

    随着现在的编程语言功能越来越成熟.复杂,内存管理也容易被大家忽略.本文将会讨论JavaScript中的内存泄漏以及如何处理,方便大家在使用JavaScript编码时,更好的应对内存泄漏带来的问题. 概 ...

  5. JavaScript如何工作:内存管理+如何处理4个常见的内存泄漏

    摘要: 作者将自己常用的JavaScript模块分享给大家. 原文:JavaScript如何工作:内存管理+如何处理4个常见的内存泄漏 作者:前端小智 Fundebug经授权转载,版权归原作者所有. ...

  6. Javascript中的内存泄漏

    最新博客站点:欢迎来访 一.内存泄漏        由于某些原因不再需要的内存没有被操作系统或则空闲内存池回收.编程语言中有多种管理内存的方式.这些方式从不同程度上会减少内存泄漏的几率,高级语言嵌入了 ...

  7. JavaScript中的内存释放

    C.C++语言需要手动管理内存的分配与释放(常用方法:malloc(), calloc(), realloc()和free()等).而JavaScript与Java.C#相似,内置了垃圾回收器,能自动 ...

  8. Unity游戏开发中的内存管理_资料

    内存是手游的硬伤——Unity游戏Mono内存管理及泄漏http://wetest.qq.com/lab/view/135.html 深入浅出再谈Unity内存泄漏http://wetest.qq.c ...

  9. 深入理解javascript中的焦点管理

    × 目录 [1]焦点元素 [2]获得焦点 [3]失去焦点[4]焦点事件 前面的话 焦点作为javascript中的一个重要功能,基本上和页面交互都离不开焦点.但却少有人对焦点管理系统地做总结归纳.本文 ...

随机推荐

  1. Educational Codeforces Round 67 E.Tree Painting (树形dp)

    题目链接 题意:给你一棵无根树,每次你可以选择一个点从白点变成黑点(除第一个点外别的点都要和黑点相邻),变成黑点后可以获得一个权值(白点组成连通块的大小) 问怎么使权值最大 思路:首先,一但根确定了, ...

  2. 最短路径问题---Floyed(弗洛伊德算法),dijkstra算法,SPFA算法

    在NOIP比赛中,如果出图论题最短路径应该是个常考点. 求解最短路径常用的算法有:Floyed算法(O(n^3)的暴力算法,在比赛中大概能过三十分) dijkstra算法 (堆优化之后是O(MlogE ...

  3. Codeforces Global Round 8 E. Ski Accidents(拓扑排序)

    题目链接:https://codeforces.com/contest/1368/problem/E 题意 给出一个 $n$ 点 $m$ 边的有向图,每条边由编号较小的点通向编号较大的点,每个点的出度 ...

  4. pbds初探

    今年暑假外校集训的时候一道题标算是最短路扩展,然而std用的是pbds,于是就产生了研究的兴趣.结果那个标程我现在死都找不到了233 定义: 在知乎上看到有oier去年向CCF发了邮件,得到的回复是p ...

  5. 【noi 2.6_6049】买书(DP)

    题意:有N元,有无限多本10.20.50和100元的书,问有几种购买方案. 解法:f[i]表示用 i 元的方案数.还有一个 j 循环这次买多少元的书. 注意--要先 j 循环,再 i 循环.因为要先考 ...

  6. hdu3577 Fast Arrangement

    Problem Description Chinese always have the railway tickets problem because of its' huge amount of p ...

  7. Atcoder ABC162 D - RGB Triplets

    传送门:D - RGB Triplets  题意:给你一个只含'R','G','B'的字符串,求有多少个长度为3且每个字符都不相等,并且第一第二和第二第三的区间长度不同的子序列. 题解:统计每个字符各 ...

  8. Windows系统自带的DOS窗口

    写在前面: 整理自网络 记录学习过程,方便复习 说明 DOS全称为Disk Operating System,意思是"磁盘操作系统" DOS是个人计算机上的一类操作系统,windo ...

  9. AC自动机算法 && 例题

    参考链接: https://blog.csdn.net/bestsort/article/details/82947639#commentBox https://blog.csdn.net/niush ...

  10. MongoDB 部署 & 基础命令

    MongoDB 官方文档 MongoDB 介绍 Mongodb 由 C++ 语言编写的,是一个基于分布式文件存储的开源数据库系统. 是专为可扩展性,高性能和高可用性而设计的数据库, 是非关系型数据库中 ...