Closed azl397985856 closed 4 years ago
放一下我浅显的回答。
Stack
是 V8 存放不可变数据
的地方,包括函数调用栈
,原始类型的值
,以及对象的引用
,因为 JS 是单线程的,所以 V8 的实现也是每个 V8 进程只有一个 Stack
。
Heap
是 V8 存放对象
或者说可变数据
的地方,GC (Garbage Collection) 指的也是回收这块地方的内存。实际上 Heap
也分成了好几个部分:
就不展开了,我也不懂。以上部分可能有不同叫法,比如 New space (Young Generation) 和 Old space (Old Generation),这两个部分的区分大概就是为了优化 GC 的。
因为 Stack
中存放的数据都是一个叠着一个的,所以放在这里的数据一定是一开始就固定好不能改变的。如果把一个对象存在 Stack
中,之后再动态给对象增加属性,不就把对象后面位置的内存空间给覆盖了嘛(如果在创建对象和增加属性之间这段时间有往 Stack
中存了其他数据的话),所以对象作为“可变数据”是存放在 Heap
中的,Heap
我的理解是“一片混沌”,有什么东西往里面一塞就行了。
简单看一下一小段代码的执行以及在这个过程中 Stack
和 Heap
的变化:
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.
栈用于管理上下文,一个上下文环境包括变量环境和词法环境,变量都放置在变量环境,如果变量是基本类型,变量直接指向值,如果变量是对象,则变量指针指向对象的内存地址,对象数据被放在堆空间。
新生代使用Scavenge算法 将新生代的内存区域分为两块,一块处于使用记为from,另外一块处于闲置中记为to。当开始进行垃圾回收的时候,检查from空间的存活对象,将存活对象拷贝到to对象并做内存整理,避免引入过多的碎片。原来的from空间现在变为to空间,原来的to空间变为新的from空间。当某个对象在生存在经过两次置换后还存活在新生代,则该对象会采取晋升策略上升到老生代。
老生代采用增量标记算法 在老生代发生内存销毁事,V8会从根节点(全局上下文)开始遍历,标记不活动对象,标记后,销毁不活动对象,并对内存进行整理,避免生成内存碎片。由于js执行是单线程的,在内存销毁时,会停止其它的js脚本以及UI线程执行(stop-the-world),为了解决这个问题,v8引入了增量标记,将整个标记过程分片。不过分片的仅仅是增量标记的过程,销毁和整理的过程还是会阻塞到js的执行。
为什么不把所有的数据都存储在栈区 V8通过ESP切换顶层栈帧,这意味着栈帧是一条结构化数据,或者说所占内存是可预测的,才可以通过寄存器计算下个上下文的内存地址,实现直接切换,如果所有可变数据都放在栈区,每个栈帧大小需要设计极大,会导致数据存储的空间复杂度高,另外即使设计空间大,还是会有溢出风险。
为什么不把数据都存在堆区 理论上是OK的,但是由于堆区的数据杂乱,GC算法复杂,会导致函数调用的上下文切换的时间复杂度高
应该js的数据都在系统的堆区,但是如果看v8模拟的,那确实分了模拟堆区和模拟栈区
V8中的内存管理是如何进行的? 什么样的会放到堆,什么样的会放到栈? 为什么要这样设计?
补充:
我们知道计算机科学并不是一门像数学那样的学科。 并没有什么绝对的对错。 而我上面举的例子是说
V8这么做,或者更准确的说V8在某一个时期是这么做的
,并不是说这种做法就是绝对的正确,其他做法是绝对的错误。 我这里想要问的其实是V8团队当时是如何考虑的?为什么要做这种设计?有什么权衡么?