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

Retained Size in Chrome memory snapshot - what exactly is being retained?

Chrome docs 保留大小 是 "the size of memory that is freed once the object itself is deleted along with its dependent objects that were made unreachable from GC roots" 这很公平。然而,即使对于简单的对象,保留的大小通常也是浅表大小的 3 倍。我知道 V8 需要存储对隐藏形状的引用,可能是 GC 等的一些数据,但有时对象有数百个额外的 "retained" 字节,当你需要有数百万个这样的对象时,这似乎是个问题.我们来看一个简单的例子:

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() ) );
}

这是内存快照:

浅尺寸为 24 字节,这与我们存储 3 x 8 字节双精度的事实完全匹配。 "Extra" 大小为 36 字节,允许存储 9 x 4 字节的指针(假设指针压缩打开)。如果我们添加三个额外的属性,额外的大小将是 72 (!) 字节,所以这取决于属性的数量。那里存储了什么?是否可以避免如此巨大的内存开销?

这里是 V8 开发人员。

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

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

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

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

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

如果您尝试用许多小的 TypedArrays 替换 3-属性 对象,您会发现内存使用量显着增加,因为 TypedArrays 的每个对象开销比简单对象大得多。他们适合拥有一些大阵列,而不是很多小阵列。