V8引擎详解(六)——内存结构

前言

本文是V8引擎详解系列的第六篇,重点内容是关于V8的内存结构,以及一般状况下内存的使用过程,本文会先从基本概念入手,了解V8的堆栈结构,最后描述一个对象建立后在内存中的生命周期(本文不会有太多GC相关内容,关于垃圾回收会在下一篇详细描述)文末会有已经完成的系列文章的连接,本系列文章还在不断更新欢迎持续关注。javascript

什么是内存

一般咱们说的计算机由5个部分组成,控制器、运算器、输出设备、输出设备、存储器,而咱们说的内存一般属于存储器,而程序运行时CPU须要调用的指令和数据只能经过内存获取(硬盘只有存储功能,执行时会将数据缓存到内存中),因此不论是什么语言的程序,运行时都依赖内存,而内存生命周期基本都是一致的:java

  • 分配所须要的内存
  • 使用分配到的内存(读、写)
  • 不须要时将其释放\归还

而不少文章讲 javascript的内存如何如何,事实上,我认为这种说法是不许确的,自己javascript只是一种语言,真正进行内存调用分配的是javascript依赖的引擎,本文就来简单聊一下V8的内存结构。面试

简述堆和栈

为何是堆和栈

在V8引擎中,能够先粗犷的分为两个部分
那栈指的就是 调用栈,首先栈的特色后进先出(普通意义上栈的特色在这里不会细说,网上相关文章不少),同时栈空间是连续的,在须要分配空间和销毁空间操做时,只须要移动下指针,因此很是适合管理函数调用。缓存

而正由于栈空间是连续的,那它的空间就注定是很是有限的,因此不方便存放大的数据,这时咱们就使用了 内存堆 来管理保存一些大数据。bash

基础类型和引用类型

这里要先说一下两种变量类型:基本类型变量引用变量类型,基础变量类型包括undefined, null, Number, String, Boolean, Symbol,而引用变量类型包括:Object、Array、Function等等,而实际上在js中Array、Function这些都是基于Objct的,咱们能够理解引用变量类型指的就是Objct。
(这里可能有人会说 null不该该是空指针对象类型吗,typeof null === 'Object'应该算是对象,事实上这里是一个设计上的历史遗留问题,而对V8系统来讲不管是null和Undefined都只是一个存在与栈里的固定的值)。ide

由于基础变量类型的值一般是简单的数据段,占用固定大小的空间,因此会存储在 中,而对象大小不定且一般会占用较大空间因此会存储在 中,而在栈空间会保存对象存储在堆空间的地址。函数

咱们将一段代码经过一张图来简单看一下。post

var a = 123;
var b = 'abc';
var c = {x: 1};
var d = 123;
var f = c;
var g = {x: 1};
复制代码

基础类型的值在建立时会开辟一块内存空间,将内存地址存储在对应的变量上,若是此时再建立一个基础类型等同于以前建立过的值,会直接将地址存储在新建立的变量上,因此就会有 a === d学习

那么若是建立一个对象,就会在堆中开辟一块空间用来存储对象,将内存地址存储在对应的变量上,若是此时建立一个新的变量(f)赋值为以前所建立的存储对象地址的变量(c),那么会将c存储的堆内存地址赋值给f,就会有 c === f大数据

若是此时再建立一个新的对象变量g,就会在堆中再开辟一块空间来建立对象,将地址赋予g,可是即便对象内容同样,地址不一样指向的也是两块空间,就会有 g !== c

关于函数调用也很好理解,也是用一段代码一张图来表示以下:

function main() {
    func1();
}
function func1() {
    func2();
    func3();
};
function func2() {};
function func3() {};
main();
复制代码

在函数间的嵌套调用的过程当中外层的函数不会释放,而栈的空间是有限的也有着严格的数量限制,因此在使用递归的时候要注意是否会 溢出

V8内存管理的核心——堆

栈的管理一般比较容易一点,经过上下移动指针来管理便可,而堆的管理相对复杂不少,而咱们一般说的垃圾回收等也主要针对堆来讲的。

堆空间的结构

咱们先来看一下内存的结构组成:

(图片来源: www.imooc.com/article/300…
V8引擎初始化内存空间主要将堆内存分为如下几个区域:

  • 新生代内存区(new space)
    新生代内存区会被划分为两个semispace,每一个semispace大小默认为16MB也就是说新生代内存区一般只有32MB大小(64位),而这两个semispace分别是from space 和 to space(具体有什么用下文会说),一般新建立的对象会先放入这两个semispace中的一个。

  • 老生代内存区(old space)
    一般会较为持久的保存对象,也分为两个区域 old pointer spaceold data space分别用来存放GC后还存活的指针信息和数据信息。

  • 大对象区(large object space)
    这里存放体积超越其余区大小的对象,主要为了不大对象的拷贝,使用该空间专门存储大对象。

  • 单元区、属性单元区、Map区(Cell space、property cell space、map space)
    Map空间存放对象的Map信息也就是隐藏类(Hiden Class)最大限制为8MB;每一个Map对象固定大小,为了快速定位,因此将该空间单独出来。

  • 代码区 (code Space)
    主要存放代码对象,最大限制为512MB,也是惟一拥有执行权限的内存

内存运行的生命周期

堆内存空间分红了有不一样功能做用的空间区域,大对象区,map区,代码区没什么好说的,重点仍是了解一下新生代内存区老生代内存区

这里咱们假设建立了一个对象 obj,先说一下新生代内存区的两个space也就是 from spaceto space 的做用。

  • 首先obj会被分配到 新生代 中两个space中其中一个space,这里咱们假设分配到了from space中。
  • 程序继续执行会不断的向from space中添加新的对象信息,这时from space将要达到了存储的上限(16MB),V8的垃圾回收机制会开始清理from中再也不被使用的对象(即没有被指向的对象)。
  • 清理后,将全部仍然存活的对象(咱们假设obj还存活),会被复制到to space而后删除全部from space中的对象。
  • 这时,程序继续运行,若是有新建立的对象会不断的分配到to space中,当to space快要满了重复执行上面说的复制转移的工做。

也就是说建立的对象会在to space 和 from space 之间转移,也就是所谓的 to --> from, from --> to 的角色互换过程。

(本文重点不是垃圾回收,因此不少GC相关的内容不会很详细)

接下来讲一下老生代内存区,如今继续看上文说的那个对象 obj:

  • 通过程序一段时间运行后的obj依然存活在新生代内存区,终于知足了晋升的条件,便转移到了老生代内存区。
  • 又过了一段时间对象 obj 终于不被引用了,同时老生代内存区域空间也被占用了不少的空间,V8就会在老生代里面进行遍历,发现了对象 obj 已经不被引用了,因而给他打了个标记。
  • 因为V8是单线程的执行机制,V8为了不一次清除占用太多时间,会给这批打了标记的待清理对象进行分批回收,至此这个对象就在内存中释放掉了。

总结

本文主要学习了一些内存的概念以及V8的内存结构,以及各部分的一些做用,事实上不管是面试仍是平常工做中,理解V8的内存机制会对咱们带来很大帮助,那在下一篇我会重点说一下V8的内存回收机制。若是有什么错误,请在评论中和做者一块儿讨论,若是您以为本文对您有帮助请帮忙点个赞,感激涕零。

参考文章

www.imooc.com/article/300…

系列文章

V8引擎详解(一)——概述
V8引擎详解(二)——AST
V8引擎详解(三)——从字节码看V8的演变
V8引擎详解(四)——字节码是如何执行的
V8引擎详解(五)——内联缓存
V8引擎详解(六)——内存结构

相关文章
相关标签/搜索