相关介绍
在设置好JS调试环境后,现在就可以使用V8了。我想要看的第一件事是各种JS类型在内存中的表现方式,JS类型分为两大类:基本类型(即数字,字符串,布尔…)和对象,我们来看看这两个类别。
Number
在JavaScript中,所有数字都是双精度数(64位浮点数),这是数字的原始类型。但是V8如何实现它们呢?在大多数情况下,在代码中使用数字时,我们只使用整数。
先来看看我之前博客文章中的截图:
在那里,我创建了一个像= [1,2,3,4,5,6,7,8,9]的数组,并在内存中识别出来。我选择了我认为是数组中的整数2。考虑到在64位系统上并且内存是小端的,我认为这是有意义的。
虽然在C语言中可能有意义,但这并不是像JavaScript这样的高级语言的工作方式。JavaScript是一种动态类型语言,这意味着变量可以包含任何类型,并且为了能够做到这一点,JavaScript需要使用变量存储一些类型信息。
JavaScript Core(WebKit JS引擎)使用NaN-boxing将类型信息和变量值存储在64位浮点内。V8使用标记指针来做到这一点。由于内存的对齐方式,指针通常指向4或8字节倍数的内存位置。这意味着指针的最后2-3位将始终为0并且永远不会被使用。V8将使用它并将在最后一位内编码某些类型信息。将会是这样的:
对于指针:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 1
对于指针,V8将始终将最后一位设置为1,如果该位为1,则表示我们正在处理指针。这也意味着在使用该指针之前,需要清除最后一位(将其设置为0),因为它被设置为1只是为了将该变量标记为指针。
对于小整数(SMI):
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 0
对于小整数(SMI),最后一位将设置为0,这意味着32位系统上的小整数为31位长。
在64位系统上,它的工作略有不同 – SMI为32位,低32位始终设为0:
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 00000000000000000000000000000000
但原理是一样的:如果一个内存位置的最后一位是1,正在处理一个指针,如果它是0,正在处理一个SMI。
通过创建以下数组并在D8中对其执行调试打印来尝试:
A = [5, “test”]
注意这里调试输出中的所有指针是如何将最后一位设置为1的,如果想要使用其中一些指针,必须清除它的最后一位,即该数组的元素地址将变为02ae456d6e98(而不是02ae456d6e99)。
让我们转到WinDbg中的那个地址并查看元素:
在那里将找到SMI 5,并在它下面是指向我们的字符串“test”的指针。如果记住内存是little-endian,SMI看起来像是十六进制:
00 00 00 05 00 00 00 00
这意味着,低32位设置为0,高32位用作SMI值。
现在让我们尝试添加一个大于32位的整数和一个浮点到数组,看看会发生什么:
看到这两个数字都存储在堆上。如果跟踪其中一个指针,将看到两件事:指向map的指针,以及IEEE 754编码形式的数字/浮点数的值:
可以使用任何IEEE 754转换器将这些值转换回十进制格式。
字符串和其他原始类型
与浮点数类似,字符串将以类似的方式存储:指向内存位置的指针:1)描述变量的映射 2)变量本身的值。一些不包含任何值的原始数据(如null或undefined)将仅表示为指向map的指针。
值得一提的是,尽管讨论了一些类型如字符串和数字作为基本类型,但也可以将它们创建为对象:
var a =“test”; //原始类型
var o = new String(“test”);
那么现在让我们谈个对象吧:)。
对象
创建一个对象并为它执行调试打印:
var o = {color:“yellow”,shape:“round”};
尝试在WinDbg中加载该对象的内存地址,因为现在唯一的目标是了解它在内存中的布局:
为了理解这一点,首先理解JS对象在内存中应该是什么样子:
++++++++++++++++++++++++
+ JS OBJECT +
++++++++++++++++++++++++
| Map |
------------------------
| Properties |
------------------------
| Elements |
------------------------
| In-Object Property 1 |
------------------------
| In-Object Property 2 |
------------------------
| ... |
------------------------
对象的内存位置的第一个指针是指向该对象的Map的指针。Map类似于隐藏类,它描述了对象的布局,属性名称到偏移的映射,以及其他一些东西,比如指向对象原型的指针。
第二个指针是指向该对象属性的指针。V8中有三种类型的对象属性:
- Very fast properites
- Fast properties
- Slow properties
如果一个对象只有几个属性,V8将直接将它们放在对象本身内。如果属性太多,V8将使用Object中的第二个指针指向包含其他属性的数组,在这种情况下可能是Fast或Slow。https://v8.dev/blog/fast-properties)
Object中的第三个指针是指向对象元素的指针。元素就像属性,但有数字名称,即{1:“green”,2:“red”},它们将在数组中使用。
在指针到达所有非常快的属性之后,如果你从上面的截图比较WinDbg内存布局中的第四个内存位置/行,你会看到它包含我们第一个属性的字符串值的地址/指针。
接下来还有更多内容要探索:)
参考资料
https://github.com/thlorenz/v8-perf/blob/master/data-types.md
https://stackoverflow.com/questions/7413168/how-does-v8-manage-the-memory-of-object-instances
https://v8.dev/blog/fast-properties
发表评论
您还未登录,请先登录。
登录