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

简介: 前言不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存,然后使用分配到的内存;最后,释放内存。而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要说那个垃圾回收机制,本文详细介绍javascript中的内存管理和垃圾回收机制。

前言

不管什么程序语言,内存生命周期基本是一致的:首先,分配需要的内存,然后使用分配到的内存;最后,释放内存。而对于第三个步骤,何时释放内存及释放哪些变量的内存,则需要说那个垃圾回收机制,本文详细介绍javascript中的内存管理和垃圾回收机制。

分配内存

为了不让程序员费心分配内存,javascript在定义变量时就完成了内存分配

img_4dbab17a41e9036b994998969907c4e6.png

有些函数调用结果是分配对象内存

img_6bbdcead3e715cd255b62e80cbd8ebec.png

有些函数分配新变量或者新对象

img_5be0a8e037025aed250e9e430c4a55c1.png

【存储方式】

因为原始值占据空间固定,是简单的数据段,为了便于提升变量查询速度,将其存储在栈中。

由于复杂值的大小会改变,所以不能将其存储在栈中,否则会降低变量的查询速度,因此其存储在堆中,存储在变量处的值是一个指针,指向存储对象的内存地址。

img_9c4efbfe2ad59133b00fe31eda0faf17.png

使用内存

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

img_d5a75632f6f3ff6b29251bcb4c222e43.png

释放内存

大多数内存管理的问题都在这个阶段。在这里碎艰难的任务是找到‘所分配的内存确实已经不再需要’

javascript内嵌了垃圾回收器,用来跟踪内存的分配和使用,以便当分配的内存不再使用的时候,自动释放它。垃圾收集器会按照固定时间间隔,或者代码执行中预定的收集时间,周期性的执行这一操作

局部变量只在函数执行的过程中存在,而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储他们的值。然后在函数中使用这些变量,直到函数执行结束。此时,局部变量就没有存在的必要了。因此可以释放他们的内存以供将来使用。在这种情况下,很容易判断变量是否还有存在的必要;但是并非所有的情况下都这么容易就能得到结论。

垃圾回收器必须跟踪哪个变量有用哪个变量没用,对于不再有用的变量打上标记,以便将来收回其所占用的内存。用于标记无用变量的策略通常有标记清楚和应用技术两种

引用计数

引用计数是最简单的垃圾收集算法,这种算法把‘对象是否需要’简化定义为“对象有没有其他对象引用它”。如果没有引用指向该对象(零引用),对象将被回收机制回收

下面代码中,量个对象a,b,被创建,一个作为另一个的属性被引用,另一个被分配给变量o

img_3b46ca6ec41d20c3a9b6012551cec7fb.png

o2引用了o

img_b31b503e12292921dddf666bfa8a3c6e.png

'这个对象'的原始引用o被o2替换了

img_40d06c8b8bda53c9591a03d7d6891b8c.png

现在“这个对象”有两个引用了,一个是o2一个是oa

img_441233db89cfb35d9435b303ace7a821.png

最初的对象现在已经是零引用了,然而它的属性a的对象还在被oa引用,所以还不能回收

img_76f99c7ee419af9c50c2e32083fb17d2.png

a属性的那个对象现在也是零引用了,它可以被回收机制回收了

img_48c6067de3a254ac5e19466e59785dbc.png

【循环引用】

Netscape Navigator3.0是最早使用引用计数策略的浏览器,但是很快它就遇到了一个严重的问题——循环引用

引用计数算法有两个限制:无法处理循环引用,在下面的例子中,两个对象被创建,并互相引用,形成一个循环。他们被调用之后不会离开函数作用域,所以他们已经没有用了,可以被回收了。然而引用计算法考虑到它们互相都有至少一次引用,所以他们不会被回收

img_a8e77989ae6a80221e3630b68adaa163.png

【IE低版本】

IE8-浏览器中,有一部分对象并不是原生javascript对象,例如,其BOM和DOM中的对象就是使用c++以COM(component Object Model 组件对象模型)对象的形式实现,而COM对象的垃圾回收机制采用的就是引用计数策略。该方式常常造成对象被循环引用时内存发生泄漏

img_b611c86f2eac7235591857905f54483a.png

这个例子在一个DOM元素(element)与一个原生javascript对象(myObject)之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象,而变量element也有一个属性名为someObject的属性指向myObject。由于存在这个循环引用,即使将例子中的DOM从页面中移除,它也永远不会被回收

  为了避免类似这样的循环引用,最好是在不使用它们的时候手工断开原生javascript和DOM元素之间的连接

img_ec2d0bda8cc6c7cf0200c9359c352f9e.png

将变量设置为null意味着切断变量与它此前引用的值之间的连接。当垃圾收集器下次运行时,就会删除这些值并回收它们占用的内存

  为了解决此问题,IE9把BOM和DOM对象都转换成了真正的javascript对象

清楚标记

javascript中最常用的垃圾收集算法是标记清除(mark-and-sweep),这个算法把‘对象是否需要’简化定义为‘对象是否可以到达’。如果对象不可以到达,对象将被垃圾回收机制回收。

大多数浏览器实现使用的都是标记清除式的垃圾收集策略,只不过垃圾收集的时间互有不同

  这个算法假定设置一个叫做根(root)的对象(在Javascript里,根是全局对象)。定期的,垃圾回收器将从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以到达的对象和所有不能到达的对象

  该算法称为标记清除,是因为分为标记(mark)和清除(sweep)两个阶段

  在标记阶段,垃圾回收器会从根对象开始遍历,每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象

  在清除阶段,垃圾回收器会对内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作

img_195cf9fcedf385c874332f20a061d180.png

 在标记阶段,从根对象1可以访问到B,从B又可以访问到E,那么B和E都是可到达对象,同样的道理,F、G、J和K都是可到达对象。在回收阶段,所有未标记为可到达的对象都会被垃圾回收器回收

【循环引用】

  使用标记清除算法,循环引用不再是问题,上面的示例中,函数调用返回之后,两个对象从全局对象出发无法获取。因此,他们将会被垃圾回收器回收

性能问题

  垃圾收集器是周期性运行的,而且如果为变量分配的内存数量很可观,那么回收工作量也是相当大的。在这种情况下,确定垃圾收集时间间隔是一个非常重要的问题

  IE的垃圾收集器是根据内存分配量运行的。具体一点说,就是256个变量,4096个对象(或数组)字面量和数组元素(slot)或者64kb的字符串。达到上述任何一个临界值,垃圾收集器就会运行

  这种实现方式的问题在于,如果一个脚本中包含那么多变量,那么该脚本很可能会在其生命周期中一直保有那么多的变量。而这样一来,垃圾收集器就不得不频繁地运行。结果,由此引发的严重性能问题促使IE7重写了其垃圾收集例程

  IE7的javascript引擎的垃圾收集例程改变了工作方式:触发垃圾收集的变量分配、字面量和数组元素的临界值被调整为动态修正。IE7中的各项临界值在初始时与IE6相等。如果垃圾收集例程回收的内存分配量低于15%,则变量、字面量和数组元素的临界值就会加倍。如果例程回收了85%的内存分配量,则将各种临界值重置回默认值。这样,极大地提升了IE在运行包含大量javascript的页面时的性能

  事实上,在有的浏览器中可以触发垃圾收集过程。在IE中,调用window.CollectGarbage()方法会立即执行垃圾收集

优化内存占用

  使用具备垃圾收集机制的javascript的主要问题在于:分配给web浏览器的可用内存数量通常要比分配给桌面应用程序的少,目的是防止运行javascript的网页耗尽全部系统内存而导致系统崩溃。内存限制问题不仅会影响给变量分配内存,同时还会影响调用栈以及在一个线程中能够同时执行的语句数量

  因此,确保占用最少的内存可以让页面获得更好的性能。而优化内存占用的最佳方式是:为执行中的代码只保存必要的数据。一旦数据不再有用,最好通过将其值设置为null来释放其引用,这种做法叫解除引用(dereferencing)。这一做法适用于大多数全局变量和全局对象的属性,局部变量会在它们离开执行环境时自动被解除引用

img_1583f1df6bc7296616037cf790595c6b.png

不过,要注意的是,解除一个值的引用并不意味着自动回收该值所占用的内存。解除引用的真正作用是让值脱离执行环境,以便垃圾收集器下次运行时将其回收

相关文章
|
22天前
|
存储 缓存 JavaScript
请描述一种JavaScript内存泄漏的情况,并说明如何避免这种情况的发生。
JavaScript内存泄漏常由闭包引起,导致无用对象滞留内存,影响性能。例如,当一个函数返回访问大型对象的闭包,即使函数执行完,对象仍被闭包引用,无法被垃圾回收。防止泄漏需及时解除引用,注意事件监听器清理,使用WeakMap或WeakSet,定期清理缓存,以及利用性能分析工具检测。
12 2
|
1月前
|
JavaScript 前端开发 算法
Java Script 中的垃圾回收机制有哪些缺点
Java Script 中的垃圾回收机制有哪些缺点
13 0
|
1月前
|
JavaScript 前端开发 算法
描述 JavaScript 中的垃圾回收机制。
描述 JavaScript 中的垃圾回收机制。
17 1
|
1天前
|
JavaScript 前端开发 Java
js 垃圾回收机制的方法
js 垃圾回收机制的方法
|
3天前
|
JavaScript 前端开发
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 内存溢出问题
FATAL ERROR: CALL_AND_RETRY_LAST Allocation failed - JavaScript heap out of memory 内存溢出问题
10 1
|
3天前
|
存储 缓存 监控
Java内存管理:垃圾回收与内存泄漏
【4月更文挑战第16天】本文探讨了Java的内存管理机制,重点在于垃圾回收和内存泄漏。垃圾回收通过标记-清除过程回收无用对象,Java提供了多种GC类型,如Serial、Parallel、CMS和G1。内存泄漏导致内存无法释放,常见原因包括静态集合、监听器、内部类、未关闭资源和缓存。内存泄漏影响性能,可能导致应用崩溃。避免内存泄漏的策略包括代码审查、使用分析工具、合理设计和及时释放资源。理解这些原理对开发高性能Java应用至关重要。
|
7天前
|
存储 前端开发 安全
JVM内部世界(内存划分,类加载,垃圾回收)(上)
JVM内部世界(内存划分,类加载,垃圾回收)
35 0
|
1月前
|
JavaScript 前端开发 Java
JavaScript的垃圾回收机制
JavaScript的垃圾回收机制
23 1
|
1月前
|
Java 程序员 Python
|
2月前
|
监控 Java 编译器
优化Go语言程序中的内存使用与垃圾回收性能
【2月更文挑战第5天】本文旨在探讨如何优化Go语言程序中的内存使用和垃圾回收性能。我们将深入了解内存分配策略、垃圾回收机制,并提供一系列实用的优化技巧和建议,帮助开发者更有效地管理内存,减少垃圾回收的开销,从而提升Go程序的性能。