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

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

相关文章
|
14天前
|
Web App开发 监控 JavaScript
监控和分析 JavaScript 内存使用情况
【10月更文挑战第30天】通过使用上述的浏览器开发者工具、性能分析工具和内存泄漏检测工具,可以有效地监控和分析JavaScript内存使用情况,及时发现和解决内存泄漏、过度内存消耗等问题,从而提高JavaScript应用程序的性能和稳定性。在实际开发中,可以根据具体的需求和场景选择合适的工具和方法来进行内存监控和分析。
|
14天前
|
JavaScript 前端开发 Java
避免 JavaScript 中的内存泄漏
【10月更文挑战第30天】避免JavaScript中的内存泄漏问题需要开发者对变量引用、事件监听器管理、DOM元素操作以及异步操作等方面有深入的理解和注意。通过遵循良好的编程实践和及时清理不再使用的资源,可以有效地减少内存泄漏的风险,提高JavaScript应用程序的性能和稳定性。
|
13天前
|
算法 JavaScript 前端开发
垃圾回收机制对 JavaScript 性能的影响有哪些?
【10月更文挑战第29天】垃圾回收机制对JavaScript性能有着重要的影响。开发者需要了解不同垃圾回收算法的特点和性能开销,通过合理的代码优化和内存管理策略,来降低垃圾回收对性能的负面影响,提高JavaScript程序的整体性能。
|
14天前
|
JavaScript 前端开发 算法
JS垃圾回收
【10月更文挑战第30天】JavaScript 的垃圾回收机制是保证程序稳定运行的重要组成部分。了解垃圾回收的原理和算法,以及注意避免内存泄漏的问题,可以帮助开发者更好地利用 JavaScript 进行高效的开发
|
27天前
|
存储 JavaScript 前端开发
JS 中的内存管理
【10月更文挑战第17天】了解和掌握 JavaScript 中的内存管理是非常重要的。通过合理的内存分配、及时的垃圾回收以及避免内存泄漏等措施,可以确保代码的高效运行和稳定性。同时,不断关注内存管理的最新发展动态,以便更好地应对各种挑战。在实际开发中要时刻关注内存使用情况,以提升应用的性能和质量。
26 1
|
13天前
|
存储 JavaScript 前端开发
JavaScript的垃圾回收机制
【10月更文挑战第29天】JavaScript的垃圾回收机制是确保程序高效运行的重要保障,了解其工作原理和相关注意事项,有助于开发者更好地编写高性能、稳定的JavaScript代码。
|
23天前
|
存储 前端开发 JavaScript
JavaScript垃圾回收机制深度解析
【10月更文挑战第21】JavaScript垃圾回收机制深度解析
97 59
|
10天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
32 6
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
63 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
19天前
|
监控 JavaScript 前端开发
如何检测和解决 JavaScript 中内存泄漏问题
【10月更文挑战第25天】解决内存泄漏问题需要对代码有深入的理解和细致的排查。同时,不断优化和改进代码的结构和逻辑也是预防内存泄漏的重要措施。
34 6