图解 Google V8
李兵
前盛大创新院高级研究员
26763 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 25 讲
图解 Google V8
15
15
1.0x
00:00/00:00
登录|注册

11 | 堆和栈:函数调用是如何影响到内存布局的?

栈帧的概念
函数调用时的栈帧指针
函数执行时的栈变化
栈的后进先出(LIFO)策略
函数具有作用域机制
函数可以被调用
分析浏览器中执行代码不会造成栈溢出但会导致页面卡死的原因
内存管理的重要性
堆的作用
栈溢出问题的解决方案
调用栈
自动垃圾回收策略
堆的内存泄漏问题
堆的数据分配和销毁
堆的特点
栈的缺点
栈的优势
栈如何管理函数调用
栈结构管理函数调用
函数资源分配和回收角度
函数调用示意图
函数特性
思考题
总结
既然有了栈,为什么还要堆?
为什么使用栈结构来管理函数调用?
堆和栈:函数调用是如何影响到内存布局的?

该思维导图由 AI 生成,仅供参考

你好,我是李兵。
相信你在使用 JavaScript 的过程中,经常会遇到栈溢出的错误,比如执行下面这样一段代码:
function foo() {
foo() // 是否存在堆栈溢出错误?
}
foo()
V8 就会报告栈溢出的错误,为了解决栈溢出的问题,我们可以在 foo 函数内部使用 setTimeout 来触发 foo 函数的调用,改造之后的程序就可以正确执行 。
function foo() {
setTimeout(foo, 0) // 是否存在堆栈溢出错误?
}
如果使用 Promise 来代替 setTimeout,在 Promise 的 then 方法中调用 foo 函数,改造的代码如下:
function foo() {
return Promise.resolve().then(foo)
}
foo()
在浏览器中执行这段代码,并没有报告栈溢出的错误,但是你会发现,执行这段代码会让整个页面卡住了。
为什么这三段代码,第一段造成栈溢出的错误,第二段能够正确执行,而第三段没有栈溢出的错误,却会造成页面的卡死呢?
其主要原因是这三段代码的底层执行逻辑是完全不同的:
第一段代码是在同一个任务中重复调用嵌套的 foo 函数;
第二段代码是使用 setTimeout 让 foo 函数在不同的任务中执行;
第三段代码是在同一个任务中执行 foo 函数,但是却不是嵌套执行。
这是因为,V8 执行这三种不同代码时,它们的内存布局是不同的,而不同的内存布局又会影响到代码的执行逻辑,因此我们需要了解 JavaScript 执行时的内存布局。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了函数调用对内存布局的影响,以及栈结构在函数调用管理中的重要性。通过比较嵌套调用、使用setTimeout和Promise的不同方式,阐述了它们在内存布局上的差异,以及这种差异对代码执行逻辑的影响。文章还介绍了函数特性和作用域机制,以及栈结构在管理函数调用关系中的自然优势。通过生动的比喻,读者可以更直观地理解栈结构的后进先出特点。此外,文章还探讨了栈和堆的区别,以及它们在内存管理中的作用。总的来说,本文通过深入浅出的方式,帮助读者理解了函数调用对内存布局的影响,以及栈结构在函数调用管理中的重要性。文章还提出了一个思考题,引发读者对代码执行过程中页面卡死的原因进行思考。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《图解 Google V8》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(35)

  • 最新
  • 精选
  • 刘大夫
    这相当于在当前这一轮任务里不停地创建微任务,执行,创建,执行,创建……虽然不会爆栈,但也无法去执行下一个任务,主线程被卡在这里了,所以页面会卡死

    作者回复: 理解的很透彻了 👍

    2020-04-10
    10
    51
  • 3Spiders
    JS的事件机制有宏任务和微任务。 宏任务是setTimeout、requestAnimationFrame、用户输入事件(I/O)等,它是由浏览器的队列完成的,在浏览器的主进程中进行,页面不会卡死。 微任务,包括Promise、MutationObserver等,是在浏览器的主线程中执行的,主线程通过一个while循环,一直拉取微任务,这样的循环会不断创建栈,再销毁栈,所以不会爆栈,但是因为在主线程中执行,所以页面会卡死。

    作者回复: 主线程通过一个while循环一直拉取微任务 这句话可以修改下:主线程在当前任务快要执行结束之前,检查微任务队列中是否存在微任务,如果有,那么那么那么当前任务会依次取出微任务队列中的微任务,并一一执行! 补充一点,微任务队列是属于当前宏任务的,所以一个宏任务中产生的微任务,只会放到它自己的微任务队列中!

    2020-04-09
    3
    18
  • Geek_49d301
    没写过js代码,猜测js应该也有两个队列,一个微任务队列一个事件队列,然后微任务队列的优先级高于事件队列,由于微任务队列一直被占用导致后面事件队列永远无法执行直到卡死

    作者回复: 微任务队列属于当前任务,也就是说,微任务队列中的微任务、一定会在当前正在执行的任务退出之前执行完毕!

    2020-04-09
    2
    6
  • 冯剑
    请问下栈帧是一个逻辑内存还是物理内存?

    作者回复: 程序使用了自己的进程空间,64位系统,每个程序的虚拟进程空间是2^64,实际上使用时,才会在物理内存中开辟空间! 所以可以说,栈空间都是程序的虚拟空间,使用的地址和实际内存中的地址是不一样的,中间还做了一层映射!

    2020-04-09
    3
    3
  • 大力
    function foo() { return Promise.resolve().then(foo) } foo() 上述代码执行后其实还是会报错的。 在执行5-10分钟后,Chrome会报错: paused before potential out-of-memory crash 然后当前宏任务继续处于被挂起状态。

    作者回复: 是的

    2020-04-26
    2
  • 桃翁
    老师,我有一个疑惑哈,就是我看你图中这些变量是按照栈来存的,那么当访问先入栈的变量的时候岂不是要把后入栈的弹出去才能访问?但是我觉得肯定不会这么做,老师能解释下怎么访问栈底部的变量么?

    作者回复: 不需要弹出来啊,比如栈帧指针地址是1000,那么变量x对应栈顶的偏移是16,那么变量x的内存位置就是1000-16(通常情况下,栈的方向是向下增长的)

    2020-04-09
    3
    2
  • Promise属于微任务会在当前事件循环执行,一直会占用主线程,导致页面无法进行渲染,所以导致页面卡死

    作者回复: 会在当前的任务中重复执行,导致当前任务无法退出,阻塞消息队列中的其它任务的执行! 我把你这句中的/当前事件循环\修改了下

    2020-04-09
    1
  • 王楚然
    思考题: 不知道对不对, 1. 不会栈溢出,是因为Promise类似setTimeout将foo放入了任务队列。 2. 会卡死,是因为,Promise会将foo放入微任务队列,该队列在每次事件循环中都需要清空才能进行下一次事件循环。对比的setTimeout是将foo放入宏任务队列,该队列每次事件循环只处理一个任务。

    作者回复: 微任务并没有进消息队列,它只是当前任务的一个未完成部分.

    2020-04-09
    2
    1
  • 就我一个人理解错了么,那个f97和f92是不是反了 应该是ebp对应f92,esp对应f97

    作者回复: 嗯 画错了,已经调整了

    2020-04-27
    2
  • wuqilv
    "答案依然是“不能”,这主要是因为 main 函数也有它自己的栈帧指针,在执行 main 函数之前,我们还需恢复它的栈帧指针。如何恢复 main 函数的栈帧指针呢?通常的方法是在 main 函数中调用 add 函数时,CPU 会将当前 main 函数的栈顶指针保存在栈中," 这里前面说的是 main 函数栈帧, 后面就变成了main 函数的栈顶,看着有点迷糊。

    作者回复: 我写错了,已经改正过来了🤧

    2020-04-10
收起评论
显示
设置
留言
35
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部