堆栈空间和垃圾回收机制

js 是什么类型的语言

我们把这种在使用之前就需要确认其变量数据类型的称为静态语言。
我们把在运行过程中需要检查数据类型的语言称为动态语言
JavaScript 就是动态语言,因为在声明变量之前并不需要确认其数据类型

JavaScript 是一种弱类型的、动态的语言

  • 弱类型,意味着你不需要告诉 JavaScript 引擎这个或那个变量是什么数据类型,JavaScript 引擎在运行代码的时候自己会计算出来
  • 动态,意味着你可以使用同一个变量保存不同类型的数据。

补充:
JS 基本类型:

  • 在ES5的时候,我们认知的数据类型确实是 6种:Number、String、Boolean、undefined、object、Null。
  • ES6 中新增了一种 Symbol 。
  • 谷歌67版本中还出现了一种 bigInt。是指安全存储、操作大整数。

JS 的数据类型有几种?

8种:Number、String、Boolean、Null、undefined、object、symbol、bigInt。

Object 中包含了哪几种类型?

包含了Data、function、Array等。这三种是常规用的。

JS的基本类型和引用类型有哪些呢?

基本类型(单类型):除Object。 String、Number、boolean、null、undefined
引用类型:object。里面包含的 function、Array、Date。

那么讲一下 基本数据类型和引用类型 存储数据有什么不同?

堆栈空间

栈空间就是我们之前反复提及的调用栈,是用来存储执行上下文的

function foo(){
    var a = 1
    var b = a
    a = 2
    console.log(a) // 2
    console.log(b) // 1
}
foo()

function foo(){
    var a = {name:"极客时间"}
    var b = a
    a.name = "极客邦" 
    console.log(a)  // {name:"极客邦"}
    console.log(b) // {name:"极客邦"}
}
foo()

从上面代码中我们可以看到第一段代码 a 改变 b 不会改变,第二段代码 a 改变 b 也改变了, 我们前面介绍了 js 的数据类型。
可以知道,第一段代码中a 和 b 都是 基本的数据类型。
第二段中a 和b 是引用类型
那么我们下面看下 基本类型和引用类型是如何存储的数据


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

执行引用类型的调用栈.png
  • 当执行一段代码时,需要先编译,并创建执行上下文
  • 执行foo 时,变量 a 和变量 b 的值都被保存在执行上下文中,你也可以认为变量 a 和变量 b 的值都是存放在栈中的。
  • JavaScript 引擎判断c的值是一个引用类型,JavaScript 引擎并不是直接将该对象存放到变量环境中,而是将它分配到堆空间里面,分配后该对象会有一个在“堆”中的地址,然后再将该数据的地址写进 c 的变量值。具体如下图:
对象类型堆栈.png

对象类型是存放在堆空间的,在栈空间中只是保留了对象的引用地址,
原始类型的数据值都是直接保存在“栈”中的,引用类型的值是存放在“堆”中的

那么为什么要分两个空间来存储呢?

  • JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率
  • foo 函数执行结束,javaScript 引擎需要离开当前的执行上下文,只需要将指针下移到上个执行上下文的地址就可以了,foo 函数执行上下文栈区空间全部回收;
调用栈切换上下文状态.png

栈空间都不会设置太大,主要用来存放一些原始类型的小数据
堆空间很大,能存放很多大的数据,不过缺点是分配内存和回收内存都会占用一定的时间。

所以回到前面的题,原始类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。

d=c的操作就是把 c 的引用地址赋值给 d

引用类型赋值.png
  • 变量 c 和变量 d 都指向了同一个堆中的对象,所以修改对象,变量都会变化,因为指向的是同一个对象。

闭包如何产生?


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())

  • 当 JavaScript 引擎执行到 foo 函数时,首先会编译,并创建一个空执行上下文

  • 在编译过程中,遇到内部函数 setName,JavaScript 引擎还要对内部函数做一次快速的词法扫描,发现该内部函数引用了 foo 函数中的 myName 变量,由于是内部函数引用了外部函数的变量,所以 JavaScript 引擎判断这是一个闭包。
    于是在堆空间创建换一个“closure(foo)”的对象(这是一个内部对象,JavaScript 是无法访问的),用来保存 myName 变量。

  • 接着继续扫描到 getName 方法时,发现该函数内部还引用变量 test1,于是 JavaScript 引擎又将 test1 添加到“closure(foo)”对象中。
    这时候堆中的“closure(foo)”对象中就包含了 myName 和 test1 两个变量了。

  • 由于 test2 并没有被内部函数引用,所以 test2 依然保存在调用栈中。

调用栈中的数据是如何回收的


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

内存模型.png
  • 一个记录当前执行状态的指针(称为 ESP),指向调用栈中 showName 函数的执行上下文,表示当前正在执行 showName 函数。

  • 当 showName 函数执行完成之后,函数执行流程就进入了 foo 函数,那这时就需要销毁 showName 函数的执行上下文了

  • JavaScript 会将 ESP 下移到 foo 函数的执行上下文,这个下移操作就是销毁 showName 函数执行上下文的过程

从栈中回收 showName 执行上下文.png
  • 上面 showName 的执行上下文虽然保存在栈内存中,但是已经是无效内存了

  • 当 foo 函数再次调用另外一个函数时,这块内容会被直接覆盖掉,用来存放另外一个函数的执行上下文。
    JavaScript 引擎会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。

堆中的数据是如何回收的

上面那段代码的 foo 函数执行结束之后,ESP 应该是指向全局执行上下文的,那这样的话,showName 函数和 foo 函数的执行上下文就处于无效状态了,不过保存在堆中的两个对象依然占用着空间

foo 函数执行结束后的内存状态.png

要回收堆中的垃圾数据,就需要用到 JavaScript 中的垃圾回收器了。

在 V8 中会把堆分为新生代和老生代两个区域,
新生代中存放的是生存时间短的对象,
老生代中存放的生存时间久的对象。

副垃圾回收器,主要负责新生代的垃圾回收
主垃圾回收器,主要负责老生代的垃圾回收。

回收流程:

标记空间中活动对象和非活动对象 ---> 回收非活动对象所占据的内存 ---->做内存整理。

副垃圾回收器:

副垃圾回收器主要负责新生区的垃圾回收。而通常情况下,大多数小的对象都会被分配到新生区,所以说这个区域虽然不大,但是垃圾回收还是比较频繁的

v8堆.png
  • 通过 Scavenge 算法 来处理 ,把新生区分为对象和空闲区域;
  • 新加入的对象,先进入对象区域,因为不大,满的时候要进行垃圾回收
  • 回收的时候,会对对象区域标记
  • 标记完成之后,把存货的对象复制到空闲区域;相当于完成了内存整理操作
  • 同时这种角色翻转的操作还能让新生代中的这两块区域无限重复使用下去。

为了执行效率,一般新生区的空间会被设置得比较小
所以很容易被存活的对象装满整个区域。所以JavaScript 引擎采用了对象晋升策略,也就是经过两次垃圾回收依然还存活的对象,会被移动到老生区中。

主垃圾回收器:

主垃圾回收器是采用标记 - 清除(Mark-Sweep)的算法进行垃圾回收的

  • 标记阶段就是从一组根元素开始,递归遍历这组根元素,在这个遍历过程中,能到达的元素称为活动对象,没有到达的元素就可以判断为垃圾数据。
标记过程.png
  • 上图可知:
    showName执行结束之后,这个时候如果遍历调用栈。是不会找到引用 1003 地址的变量,也就意味着 1003 这块数据为垃圾数据
  • 由于 1050 这块数据被变量 b 引用了,所以这块数据会被标记为活动对象
  • 最后整理,排序
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容