Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【每日一题】- 2020-05-18 - V8中栈内存和堆内存的选择 #129

Closed
azl397985856 opened this issue May 18, 2020 · 4 comments
Closed

Comments

@azl397985856
Copy link
Owner

V8中的内存管理是如何进行的? 什么样的会放到堆,什么样的会放到栈? 为什么要这样设计?

补充:

我们知道计算机科学并不是一门像数学那样的学科。 并没有什么绝对的对错。 而我上面举的例子是说V8这么做,或者更准确的说V8在某一个时期是这么做的,并不是说这种做法就是绝对的正确,其他做法是绝对的错误。 我这里想要问的其实是V8团队当时是如何考虑的?为什么要做这种设计?有什么权衡么?

@azl397985856 azl397985856 added the Daily Question 每日一题 label May 18, 2020
@azl397985856 azl397985856 added this to 待认领 in 每日一题认领区 May 18, 2020
@suukii
Copy link

suukii commented May 18, 2020

放一下我浅显的回答。

Stack 是 V8 存放不可变数据的地方,包括函数调用栈原始类型的值,以及对象的引用,因为 JS 是单线程的,所以 V8 的实现也是每个 V8 进程只有一个 Stack

Heap 是 V8 存放对象或者说可变数据的地方,GC (Garbage Collection) 指的也是回收这块地方的内存。实际上 Heap 也分成了好几个部分:

  • New space 存放新对象以及生命周期很短的对象;
  • Old space 存放在 New space 中没有被清理掉的对象,也就是生命周期比较长的对象;
  • Large object space 如果对象太大,超过了其他部分的存储限制,就会被放到这里来;
  • Code-space 用来放 JIT 编译器编译好的代码块;
  • Cell space, property cell space, map space 这里的对象大小都相等,而且只能存放一些特定的对象;

就不展开了,我也不懂。以上部分可能有不同叫法,比如 New space (Young Generation) 和 Old space (Old Generation),这两个部分的区分大概就是为了优化 GC 的。

因为 Stack 中存放的数据都是一个叠着一个的,所以放在这里的数据一定是一开始就固定好不能改变的。如果把一个对象存在 Stack 中,之后再动态给对象增加属性,不就把对象后面位置的内存空间给覆盖了嘛(如果在创建对象和增加属性之间这段时间有往 Stack 中存了其他数据的话),所以对象作为“可变数据”是存放在 Heap 中的,Heap 我的理解是“一片混沌”,有什么东西往里面一塞就行了。

简单看一下一小段代码的执行以及在这个过程中 StackHeap 的变化:

stack_heap_v8

  • 全局作用域(Global frame)一直在栈底
  • 每个函数调用时,V8 都会向 Stack 中入栈一个 frame
  • 这个 frame 里面存放着该函数的本地变量、参数以及返回值
  • 原始类型的值比如数字和字符串都是直接保存在 Stack 中的
  • 对象类型,比如普通对象和函数,都是存放在 Heap 中,然后在 Stack 中存放一个 pointer 指向对象在 Heap 中的实体
  • 调用函数时会向 Stack 入栈一个 frame,函数 return 后这个 frame 会被出栈
  • 如果 Heap 中的对象在 Stack 中没有 pointer 指向它,它就成“孤儿”了,就可以被 GC 回收了

@azl397985856 azl397985856 pinned this issue May 19, 2020
@stale
Copy link

stale bot commented Jul 17, 2020

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Jul 17, 2020
@stale stale bot closed this as completed Jul 24, 2020
@feikerwu
Copy link
Contributor

feikerwu commented Aug 4, 2020

前置:

  • v8中数据主要分到两块存储,分别是堆栈
  • js有7种基本数据类型和一个object类型

栈用于管理上下文,一个上下文环境包括变量环境和词法环境,变量都放置在变量环境,如果变量是基本类型,变量直接指向值,如果变量是对象,则变量指针指向对象的内存地址,对象数据被放在堆空间。

内存销毁

  • 栈:通过ESP,直接切换栈帧,也就是切换上下文,快速实现内存销毁
  • 堆: 堆中根据边际效应,将堆区设计为的老生代和新生代两块区域,新生代用于存储新创建的小对象,老生代用于存取访问超过一定次数或者大对象
    新生代和老生代使用不同的内存销毁算法
  1. 新生代使用Scavenge算法
    将新生代的内存区域分为两块,一块处于使用记为from,另外一块处于闲置中记为to。当开始进行垃圾回收的时候,检查from空间的存活对象,将存活对象拷贝到to对象并做内存整理,避免引入过多的碎片。原来的from空间现在变为to空间,原来的to空间变为新的from空间。当某个对象在生存在经过两次置换后还存活在新生代,则该对象会采取晋升策略上升到老生代。

  2. 老生代采用增量标记算法
    在老生代发生内存销毁事,V8会从根节点(全局上下文)开始遍历,标记不活动对象,标记后,销毁不活动对象,并对内存进行整理,避免生成内存碎片。由于js执行是单线程的,在内存销毁时,会停止其它的js脚本以及UI线程执行(stop-the-world),为了解决这个问题,v8引入了增量标记,将整个标记过程分片。不过分片的仅仅是增量标记的过程,销毁和整理的过程还是会阻塞到js的执行。

  • 为什么不把所有的数据都存储在栈区
    V8通过ESP切换顶层栈帧,这意味着栈帧是一条结构化数据,或者说所占内存是可预测的,才可以通过寄存器计算下个上下文的内存地址,实现直接切换,如果所有可变数据都放在栈区,每个栈帧大小需要设计极大,会导致数据存储的空间复杂度高,另外即使设计空间大,还是会有溢出风险。

  • 为什么不把数据都存在堆区
    理论上是OK的,但是由于堆区的数据杂乱,GC算法复杂,会导致函数调用的上下文切换的时间复杂度高

@eric-gitta-moore
Copy link

应该js的数据都在系统的堆区,但是如果看v8模拟的,那确实分了模拟堆区和模拟栈区

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
Development

No branches or pull requests

4 participants