Node.js 与 Chrome 中的 JavaScript 对象占用多少内存?

问题描述 投票:0回答:1

我不明白为什么堆大小是应有的两倍。

我创建了一个完美的二叉树。我猜 v8 知道每个节点有 3 个字段。

function buildTree(depth) {
  if (depth === 0) return null;

  return {
    value: 0,
    left: buildTree(depth - 1),
    right: buildTree(depth - 1),
  };
}

我假设 1 个数字占用 8 个字节,2 个对象引用各占用 8 个字节。因此,一个树节点总共需要 24 个字节。

然后我运行下面的代码:

const tree = buildTree(25);
// 2 ** 25 - 1 ≈ 33_500_000 nodes
// 33_500_000 nodes × 24 bytes = 840_000_000 bytes

const { heapUsed } = process.memoryUsage();
const expectedSize = (N * 24 / 1e6).toFixed(2) + " MB";
const actualSize = (heapUsed / 1e6).toFixed(2) + " MB";

console.table({ expectedSize, actualSize });

// ┌──────────────┬──────────────┐
// │ expectedSize │ '805.31 MB'  │
// │  actualSize  │ '1614.08 MB' │
// └──────────────┴──────────────┘

然后我尝试在 Chrome 中运行这段代码。我只是打开 devtools,在“控制台”选项卡中运行代码,然后切换到“内存”选项卡,如我所料看到 807 MB。

v8 是如何工作的?为什么需要2倍的内存?与 Chrome 相比有什么区别?

javascript google-chrome-devtools heap-memory v8
1个回答
0
投票

(这里是 V8 开发人员。我重新提出了这个问题,因为关键点“为什么这个对象树在 Node 中需要的内存是 Chrome 中的两倍?”,链接的问题尚未回答。)

这有两个部分。

第 1 部分:对象布局。 JavaScript 等语言的虚拟机中的对象具有内部对象标头。这里跳过细节,结果是在 V8 中,每个 JS 对象在其内部标头中有 3 个指针。您的示例创建的特定对象还需要另外 3 个指针来存储其属性(

value
left
right
),每个对象总共需要 6 个指针。

第 2 部分:指针压缩。 V8 在 64 位平台上支持“指针压缩”,其中每个堆上指针仅使用 32 位(即指针及其目标都驻留在垃圾收集堆上)。该技术有两个明显的含义:
(1) 它将垃圾收集堆的可寻址大小限制为 2**32 == 4 GiB。
(2) 它将指针的内存消耗减少了一半。字符串、数字等数据的内存消耗不受影响;实际上,这意味着应用程序的总体内存消耗通常会减少三分之一左右。

现在我们可以解释发生了什么:
在 Chrome 中,启用了指针压缩,因此每个 6 字段对象需要

6*4
字节,因此您的示例总共需要
2**25 * 6*4
= 8.05 亿字节。
Node.js 团队认为支持大于 4 GiB 的堆比节省三分之一的内存消耗更重要,因此在构建 Node 时,他们在关闭指针压缩的情况下编译 V8,因此每个 6 字段对象需要
6*8
字节,所以你的示例总共需要
2**25 * 6*8
= 16 亿字节。


附注一个额外的细节:本例中对象中存储的数字实际上始终是相同的数字 0,因此 V8 使用其指针标记技术将其存储为某种“假指针”。这既不是 64 位 IEEE 格式,也不是单独的对象;如果您想了解更多信息,请查找术语“smi tagging”。如果您修改示例以将

value: Math.random()
存储在对象中,您会发现 Chrome 中每个对象的内存消耗增加 12 字节,Node 中每个对象的内存消耗增加 16 字节。

© www.soinside.com 2019 - 2024. All rights reserved.