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

内存生命周期:

  1. 分配内存
  2. 使用分配的内存(读与写操作)
  3. 当应用程序不再需要时,释放掉已分配的内存

虽然垃圾回收器能能自动管理内存分配、释放,但并不意味着开发者不再需要关注内存管理。因为一些不好的编码会导致内存泄露,即应用程序不再需要的内存没有被释放掉。因此了解内存管理是很重要的。

Javascript中的内存分配

当声明变量时,JavaScript会自动为变量分配内存

var numberVar = 100; //为整数分配内存
var stringVar = 'node simplified'; // 为字符串分配内存
var objectVar = {a: 1}; // 为对象分配内存
var a = [1, null, 'abra']; // 为数组分配内存
function f(a) {
return a + 2;
} // 为函数分配内存

GC(Garbage collection)

垃圾回收是追踪并释放应用程序不再使用的内存过程。垃圾回收器通过算法来实现追踪应用程序不再使用的内存。主要涉及的垃圾回收算法如下:

  • Reference-counting garbage collection(引用计数)
  • Mark-and-sweep algorithm(标记清除)

Reference-counting garbage collection(引用计数)

引用计数算法是一种最基础的垃圾回收算法,当一个对象的引用数为零时,会被自动回收。该算法将一个对象的引用数为0时视为应用程序不再需要的内存。

!function (){
var o1 = {a: {b: 2}},// 两个对象被创建。假如分别用A:{a: {b: 2}},B:{b: 2}表示,对象B被对象A的属性a引用,对象A被赋值给变量o1。A和B的引用数都为1,因此不能被回收。
o2 = o1; // 将对象A赋给变量o2。此时A引用数为2,B引用数1。
o1 = 1;// 将变量o1对对象A引用切断。此时A引用数为1,B引用数1。
var oa = o2.a; // 将对象B赋值给变量oa。此时A引用数为1,B引用数2。
o2 = 'foo'; // 将变量o2对对象A引用切断。此时A引用数为0,B引用数1。因为对象A的a属性被变量oa引用,因此对象A不能被释放。
oa = null; // 将变量oa对对象B引用切断。此时A引用数为0,B引用数0。A与B会被回收。
}()

引用计数的限制:循环引用

循环引用存在一个限制。如下实例,两个对象相互引用,形成一个循环引用。正常情况下,当函数执行完后,对应的内存会被释放掉。而引用计数算法会将循环引用对象的引用数都视为至少为1,因此不能被回收。

function f() {
var o = {};
var o2 = {};
o.a = o2; // o references o2
o2.a = o; // o2 references o return 'azerty';
} f();

常见问题

IE6-7的DOM对象是基于计数引用算法进行垃圾回收的。而循环引用通常会导致内存泄露:

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

如上述实例,DOM元素div通过自身的“circularReference”属性循环引用自己。如果没有显式将该属性删除或设为null,计数引用垃圾回收器会始终持有至少一个引用。即使DOM元素从DOM树种移除,DOM元素的内存会一直存在。如果DOM元素持有一些数据(如实例中“lotsData”属性),该数据对应的内存也无法被释放。了解更多参考--->IE<8循环引用导致的内存泄露

Mark-and-sweep algorithm(标记清除)

该算法将“对象不再需要”的定义简化为“对象不可到达”。 这个算法假设有一组被称为roots的对象(在JavaScript中,root就是全局对象)。垃圾回收器会定期地从这些roots开始,查找所有从根开始引用的对象,然后再查找这些对象引用的对象……。从roots开始,垃圾回收器会查找所有可到达对象,并回收不可到达的对象。

为了确定对象是否需要,该算法要确定对象是否可到达。由如下步骤组成:

  1. 垃圾回收器会创建一组roots,roots通常是持有引用的全局变量。在JavaScript中,window对象就可作为root的全局变量。
  2. 垃圾回收器会检查所有的roots并标记为活跃状态。然后递归遍历所有的子变量。只要从root不能到达的都被标记为垃圾。
  3. 所有没有被标记为活跃状态的内存块都被视为垃圾。垃圾回收器就可以释放这部分内存并把释放的内存返回给操作系统。

这个算法比引用计数算法更优,因为对于引用计数算法“零引用的对象”总是不可到达的,但反之则不一定,如循环引用。而标记清除算法不存在循环引用的问题。

截至2012年,所有现代浏览器都内置了标记清除垃圾回收器。在过去几年里所有对JavaScript垃圾回收算法的改进(generational/incremental/concurrent/parallel garbage collection)都是基于标记清除算法来实现的,但并没有改变标记清除算法本身和它对“对象不再需要”定义的简化。

循环引用不再是问题

前面循环引用的实例中,在函数执行完后,两个对象不再被全局对象可访问的对象引用。因此这两个对象被垃圾回收器标记为不可到达,接着被回收掉。 

限制:需要明确无法到达的对象

对于这个限制,实践中很少遇见,所以开发者不太会去关心垃圾回收机制。

参考文章:

原文地址: https://github.com/cucygh/js-leakage-patterns/blob/master/JavaScript%E5%86%85%E5%AD%98%E9%82%A3%E7%82%B9%E4%BA%8B/JavaScript%E5%86%85%E5%AD%98%E9%82%A3%E7%82%B9%E4%BA%8B.md

最新文章

  1. 【Spring】获取资源文件+从File+从InputStream对象获取正文数据
  2. mysql C API的使用
  3. iOS 学习 - 1.代理传值
  4. Android Touch(1)事件的传递流程(*)
  5. 系列文章--SharePoint 开发教程
  6. uva 408 Uniform Generator
  7. 前端需要注意的seo
  8. 二叉搜索树的第 k 个结点
  9. Omron 论坛软件下载连接
  10. 微信小程序之滑动日历展示
  11. django rest framework(3)
  12. xml模块、项目开发过程
  13. IOS应用内购(一)内购的种类
  14. guacamole部署
  15. linux文件管理 文件权限
  16. 梯度下降法实现-python[转载]
  17. 18、docker的持久化存储和数据共享
  18. mysql 修改数据库存储地址
  19. spring整合ehcache 注解实现查询缓存,并实现实时缓存更新或删除
  20. SQLPLUS的乱码问题

热门文章

  1. Java 的 ArrayList 的底层数据结构
  2. 跨域带cookie失效的解决方案
  3. 在Docker内安装jenkins运行和基础配置
  4. [转]&lt;版本一&gt;写代码的小女孩
  5. 吴裕雄--天生自然KITTEN编程:一箭穿心
  6. js中判断为false的情况
  7. 用Python拨打电话
  8. 关于PHPExcel的一些资料
  9. 远程桌面协议RDP
  10. 初学qt——提示窗体