一文详解Javascript内存机制与垃圾回收

 更新时间:2023年06月15日 10:32:49   作者:越谷  
这篇文章主要给大家详细介绍了Javascript的内存机制与垃圾回收,文中又详细的代码示例,对我们学习Javascript有一定的帮助,需要的同学可以借鉴阅读

一 内存机制

1.1 数据类型

Javascript 是一种动态的(在运行过程中检查数据类型,)、弱类型(同一个变量可以保存不同类型的数据)的语言。

Javascript 数据类型一共有 8 种: Boolean,Null,Undefined,Number,BigInt,String,Symbol,Object。前 7 种数据类型为原始类型Object引用类型。两种类型的数据在内存中存放的位置不同。

1.2 内存空间

Javascript 在执行的过程中,主要有三种类型的内存空间,分别是:代码空间、栈空间、堆空间。

代码空间主要是存储可执行代码的。

原始数据类型存储在栈空间中, 引用数据类型存储在堆空间中。

说明示例:

function foo(){
    var a = "极客时间"
    var b = a
    var c = {name:"极客时间"}
    var d = c
}
foo()

栈空间,也就是之前提起的调用栈,用来存储执行上下文。

上述代码执行到第 3 行的时候,变量 a 和 b 的值都直接保存在执行上下文中,执行上下文又被压入栈空间中,所以可以理解为变量 a 和 b 都存放在栈空间中。

当执行到第 4 行,Javascript 引擎判断变量 c 的值是引用类型,Javascript 引擎将该值分配到堆空间里,分配后该值会有一个在堆空间的地址,然后将该地址赋值给变量 c。第 5 行,将 c 赋值给 d,实际是将引用地址赋值给了 d,修改引用类型对象的值,改的是堆空间中数据的值。

栈空间因为要维护执行上下文,影响程序的执行效率,所以空间一般比较小,可以用来存放原始类型的小数据;引用类型的数据可以很大,所以堆空间比较大,不过堆空间分配内存和回收内存会占用一定的时间。

栈空间切换执行上下文状态:

1.3 闭包内的数据存储

闭包内的变量存储到栈空间还是堆空间?

说明示例:

function foo() {
    var myName = "极客时间"
    let test1 = 1
    const test2 = 2
    var innerBar = { 
        setName:function(newName){
            myName = newName
        },
        getName:function(){
            console.log(test1)
            return myName
        }
    }
    return innerBar
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())

执行上述代码,当 foo 函数执行完之后,调用栈中的 foo 函数的执行上下文会被销毁,由于变量 myName 和 test1 持续存在外部引用,Javascript 引擎判断这是一个闭包,会在堆空间中创建一个closure(foo)的对象,这个对象中包含这两个被引用的变量。

二 垃圾回收

一些数据在使用后就不再需要了,这部分数据继续存在内存里,并且逐渐积累,会占用越来越多的空间,通过垃圾回收机制以释放有限的内存空间。

2.1 不同语言的垃圾回收策略

一般,垃圾回收分为手动回收自动回收两种策略。

以 C / C++ 为例,使用的是手动回收策略,内存的分配与销毁都是由程序员写的代码控制的,如果不主动销毁垃圾数据,将导致内存泄漏。

以 Javascript / Java 为例,垃圾数据由内置的垃圾回收器自动回收,不需要通过编写代码手动释放。

2.2 调用栈的垃圾回收机制

Javascript 引擎把执行上下文压入调用栈的同时,使用一个记录当前执行状态的指针(ESP)指向当前的执行上下文,表示正在执行该执行上下文。

当该执行上下文运行完毕,ESP 下移,这个下移的操作就是销毁被执行过的上下文的过程。

示例说明:

function foo(){
    var a = 1
    var b = {name:"极客邦"}
    function showName(){
      var c = 2
      var d = {name:"极客时间"}
    }
    showName()
}
foo()

2.3 堆空间垃圾回收机制

2.3.1 代际假说

代际假说观点认为,大部分对象在内存中有用的时间很短,一经分配内存,很快就变得不可访问;少量数据持续活跃,活的很久。

2.3.2 V8 垃圾回收机制:分代收集

基于代际假说,V8 把堆分为老生代新生代,新生代中存放的是生存时间短的对象(1—8M空间),老生代中存放的是生存时间久的对象(空间比较大)。

针对新生代和老生代,V8 使用不用的回收机制,以便更高效的进行垃圾回收。使用主垃圾回收器回收老生代的垃圾数据,使用副垃圾回收器回收新生代的垃圾数据。

垃圾回收原理

  • 标记空间中的活动对象(还在使用的对象)和非活动对象(可以回收的对象)。
  • 标记完成之后,统一清理内存中所有被标记的可回收对象,回收这部分对象占据的内存。
  • 内存整理:频繁回收对象之后,内存中存在大量不连续空间(内存碎片),如果要分配较大连续内存时,可能出现内存不足的情况,所以需要将内存碎片成连续的空间。

副垃圾回收器

主要负责新生代的垃圾回收。新生代空间较小,里面的对象一般较小;回收频繁。

回收机制采用 Scavenge 算法。该算法把新生代空间划半分为对象区域空闲区域

新加入的对象存到对象区域,当对象区域快被写满时,执行一次垃圾回收。在回收过程中,对对象区域里的对象做标记,标记完成后,把存活的对象复制到空闲区域中并有序的排列起来(消除内存碎片),把无用的对象清理掉。

复制完成后,对象区域和空闲区域进行角色翻转,对象区域变成空闲区域,空闲区域变成对象区域。这样这两块区域可以无限重复使用下去。

复制操作需要时间成本,因此为了执行效率,新生区的空间一般设置的比较小。

经过两次垃圾回收依然存活的对象,会被移动到老生区中,以防止过多的存活对象挤满新生区。

主垃圾回收器

主要负责老生区的垃圾回收。老生区的里的对象一般占用空间大(因复制操作耗时所以不适合使用 Scavenge 算法),存活的时间比较长。

回收机制采用的是 标记-清除(Mark-Sweep)算法。从一组根元素开始,递归遍历这组根元素,在遍历过程中能到达的元素称为活动对象,没有达到的元素判断标记为垃圾数据。

标记完成之后,执行清除过程。

如上图,清除后会产生大量不连续的内存碎片;为了避免内存碎片,进化出了另一种算法:标记-整理(Mark-Compact),标记完成后,让所有存活的对象移向内存一端,然后直接清理掉端边界外的数据。

全停顿

Javascript 是运行在主线程上的(单线程),一旦执行垃圾回收算法,其他 Javascript 脚本将被阻塞,需要等待垃圾回收完毕才能继续执行。这种行为叫做全停顿。

如上图,过长的全停顿将导致页面卡顿。新生代因其空间小存活对象占用空间小,对全停顿影响不大,老生代的垃圾回收是造成全停顿的主因。为了降低老生代垃圾回收造成的卡顿,V8 将标记过程分为一个个子标记,让子标记和 Javascript 脚本交替进行,这个解决方案叫做增量标记算法。

以上就是一文详解Javascript内存机制与垃圾回收的详细内容,更多关于Javascript内存机制与垃圾回收的资料请关注脚本之家其它相关文章!

相关文章

  • JavaScript 中如何拦截全局 Fetch API 的请求和响应问题

    JavaScript 中如何拦截全局 Fetch API 的请求和响应问题

    在本文中,我们介绍了什么是 JavaScript 拦截器,学习了如何通过给 Fetch API 使用猴子补丁和使用 fetch-intercept 库来创建拦截器,对js拦截全局Fetch API的请求和响应知识感兴趣的朋友跟随小编一起看看吧
    2023-01-01
  • JavaScript将字符转换为ASCII码的实现方法

    JavaScript将字符转换为ASCII码的实现方法

    在Web前端开发中,字符编码是处理文本数据时不可或缺的一部分,ASCII是一种广泛使用的字符编码标准,它为每个字符分配了一个唯一的数字表示,解如何将字符转换为ASCII码,对于解析、生成和操作字符串具有重要意义,本文将详细介绍这一过程,并提供多个实用的代码示例
    2024-12-12
  • TypeScript中实现字符串格式化及数值对齐

    TypeScript中实现字符串格式化及数值对齐

    字符串格式化是处理文本展示的常见需求,尤其在需要规范数值、日期等数据的显示格式时非常重要,TypeScript可以通过模板字符串、字符串方法和正则表达式组合实现类似功能,包括数值的左右对齐等场景,本文给大家介绍的非常详细,需要的朋友可以参考下
    2025-08-08
  • javascript二维数组转置实例

    javascript二维数组转置实例

    这篇文章主要介绍了javascript二维数组转置方法,实例分析了数组行列交换的转置技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-01-01
  • 微信小程序事件绑定传参冒泡及捕获的示例详解

    微信小程序事件绑定传参冒泡及捕获的示例详解

    这篇文章主要为大家介绍了微信小程序事件绑定传参冒泡及捕获的示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步早日升职加薪
    2022-04-04
  • JS获取一个未知DIV高度的方法

    JS获取一个未知DIV高度的方法

    这篇文章主要介绍了JS获取一个未知DIV高度的方法,涉及javascript针对页面元素属性的动态操作相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2016-08-08
  • JavaScript常用数学函数用法示例

    JavaScript常用数学函数用法示例

    这篇文章主要介绍了JavaScript常用数学函数用法,结合实例形式分析了JavaScript常见的对数、平方、绝对值、正弦、四舍五入等相关数学函数使用技巧,需要的朋友可以参考下
    2018-05-05
  • 微信小程序在{{ }}中直接使用函数的方法示例

    微信小程序在{{ }}中直接使用函数的方法示例

    最近在学习微信小程序,在学习中遇到了一些问题,所以进行了总结了下,这篇文章主要给大家介绍了关于微信小程序在{{ }}中直接使用函数的相关资料,需要的朋友可以参考下
    2021-06-06
  • 第五篇Bootstrap 排版

    第五篇Bootstrap 排版

    使用bootstrap的排版特性可以创建标题,段落,列表及其它内联元素。本文重点给大家介绍Bootstrap 排版 知识,非常不错,具有参考借鉴价值,感兴趣的朋友一起学习吧
    2016-06-06
  • js控制台输出的方法(详解)

    js控制台输出的方法(详解)

    下面小编就为大家带来一篇js控制台输出的方法(详解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-11-11

最新评论