Chrome 内存快照中的保留大小 - 究竟保留了什么?

pra*_*gma 10 javascript memory google-chrome v8 google-chrome-devtools

Chrome 文档保留大小是“一旦对象本身被删除后释放的内存大小,以及从 GC 根无法访问的依赖对象”,这很公平。然而,即使对于简单的对象,保留大小通常是浅层大小的 3 倍。我知道 V8 需要存储对隐藏形状的引用,可能是一些用于 GC 的数据等等,但有时对象有数百个额外的“保留”字节,当您需要拥有数百万个此类对象时,这似乎是一个问题。我们来看一个简单的例子:

class TestObject {
    constructor( x, y, z ) {
        this.x = x;
        this.y = y;
        this.z = z;
    }
}

window.arr = [];
for ( let i = 0; i < 100000; i++ ) {
    window.arr.push( new TestObject( Math.random(), Math.random(), Math.random() ) );
}
Run Code Online (Sandbox Code Playgroud)

这是内存快照:

内存快照,浅层大小为 24 字节,保留大小为 60 字节

浅层大小为 24 字节,这与我们存储 3 x 8 字节双精度的事实完美匹配。“额外”大小是 36 字节,它允许存储 9 x 4 字节的指针(假设指针压缩已打开)。如果我们添加三个额外的属性,额外的大小将是 72 (!) 字节,因此这取决于属性的数量。那里存放的是什么?是否有可能避免如此大量的内存开销?

jmr*_*mrk 21

V8 开发人员在这里。

浅尺寸是对象本身,由标准对象头(3 个指针)和 3 个对象内属性组成,它们也是指针。那是 6 个(压缩的)指针,每个指针 4 个字节 = 24 个字节。

额外的保留大小是三个属性的存储空间。它们中的每一个都是一个“HeapNumber”,由一个 4 字节的映射指针和一个 8 字节的有效负载组成。所以这是 3 个属性乘以 12 个字节 = 36 个字节。(有了这些知识,使用另外三个可能也是数字的属性,这将翻倍至 72,这应该不足为奇。)

加起来,每个对象总共占用24+36=60个字节。

Map 和prototype 不计入每个对象的保留大小,因为它们被所有对象共享,因此释放一个对象不会让它们也被释放。

节省内存的一个想法(如果您觉得这很重要)是“转置”您的数据组织:不是 1 个包含 100,000 个对象且每个对象有 3 个数字的数组,而是可以有 1 个对象包含 3 个数组,每个数组包含 100,000 个数字。根据您的用例,这可能是也可能不是可行的方法:如果数字的三元组来来去去很多,那么将它们存储在一个巨大的数组中会令人不快;而如果它是一个静态数据集,那么两种模型在可用性上可能相当。如果你这样做,你就可以避免重复的每个对象开销;此外,数组可以内联存储双数(只要整个数组包含数字),因此您可以存储相同的 300K 数字,而总内存消耗仅为 2.4MB。

如果您尝试用许多小的 TypedArray 替换 3-property 对象,您会看到内存使用量显着增加,因为 TypedArray 的每个对象开销比简单对象大得多。它们旨在拥有一些大型阵列,而不是许多小型阵列。