阿里妹导读
本文深入探讨了JavaScript对象在V8引擎中的内存管理和优化策略,特别是在处理大规模数据时可能出现的性能和内存问题。
背景
开发某JS应用时使用了一个较大的数据列表,在探究性能和内存过程中,观察到了反常的数据内存变化,从而引发了本文相关内容的研究。本文绝大部分对象设计和实现细节的内容和结论来自于V8的源码阅读、以及Chrome上的JS实验,如有错误欢迎指出纠正。
引子
假设有100,000*100的数据存储在一个JSON文件中,表达形式是一个含10万个对象的数组,其中每个对象有相同的100个属性,属性名和属性值非常简单,比如{"a0":0, "a1":0,"a2":0...}。
JSON.parse加载此数据后,JS内存占用是42.6MB(所有Chrome内存汇报都已经过垃圾回收)。
arr.forEach((item) => { delete item[`a0`]; }))
删除一个属性,大家以为内存有会变化吗?刚开始我以为数据量没变内存变化不会太大,然而JS堆内存飙升到324MB,内存却增加近8倍,为什么?
你可能会发觉在这些特定的场景下,JS对象的存储结构发生了变化,事实确实如此。Chrome的内核是V8引擎,V8是如何设计JS对象,对象什么情况下会发生存储结构变化,如何避免和削弱负面影响,这是本文探讨的几个问题。
相关测试代码如下:
// 创建一个空数组
const data = [];
// 生成100个对象
for (let i = 0; i < 100000; i++) {
// 创建一个空对象
const obj = {};
// 生成100个属性
for (let j = 0; j < 100; j++) {
// 属性名和属性值都是数字
const propName = `a${j}`;
const propValue = 0;
obj[propName] = propValue;
}
// 将对象添加到数组中
data.push(obj);
}
// 将数据转换为JSON字符串
const jsonString = JSON.stringify(data);
// 将JSON字符串写入文件
const fs = require('fs');
fs.writeFileSync('data.json', jsonString);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="click">点击</button>
<div>点击按钮执行 arr.forEach((item) => { delete item[`a0`]; })</div>
<script>
var xhr = new XMLHttpRequest();
// 方便在Heap Snapshot观测
function createObject(data) {
this["json"] = data;
}
var obj = new createObject();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var data = JSON.parse(this.responseText);
obj["json"] = data;
}
};
xhr.open("GET", "data.json", true);
xhr.send();
const btn = document.getElementById("click");
btn.addEventListener("click", () => {
obj.json.forEach((item) => {
delete item[`a0`];
});
});
</script>
</body>
</html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<button id="click">点击</button>
<div>点击按钮执行 arr.forEach((item) => { delete item[`a0`]; })</div>
<script>
var xhr = new XMLHttpRequest();
// 方便在Heap Snapshot观测
function createObject(data) {
this["json"] = data;
}
var obj = new createObject();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
var data = JSON.parse(this.responseText);
obj["json"] = data;
}
};
xhr.open("GET", "data.json", true);
xhr.send();
const btn = document.getElementById("click");
btn.addEventListener("click", () => {
obj.json.forEach((item) => {
delete item[`a0`];
});
});
</script>
</body>
</html>
JSObject基本结构
JSObject最少会有三个指针,分别指向HiddenClass,Properties store和Elements store,V8中的Map在一些文章中也被叫做hidden class,本文出现的所有Map均指hidden class,简单来说Map用于描述对象的结构数据的。这里引用V8官方文档中的一张图。
如何避免或削弱对象结构变换带来的负面影响
当对象被设置成为一个函数(或对象)的原型时会从Dictionary Mode优化成为Fast Mode
/ node --allow-natives-syntax xxx.js
function toFastProperties(o) {
function A() {
this.x = 'x'
}
A.prototype = o;
const a = new A();
function ic() {
return typeof a.b;
}
ic();
ic();
return o;
}
const o = {a:1,b:2};
console.log(%HasFastProperties(o)); // true
delete o.a;
console.log(%HasFastProperties(o)); // false
toFastProperties(o);
console.log(%HasFastProperties(o)); // true
在设置对象为函数原型后,又进行了实例化和两次属性查询,感兴趣的可以看下V8系列中的 lnline Caches 或其它有关的博文。
使用JSON.stringify和JSON.parse解析对象
const o = {};
for (let i = 0; i < 127; ++i) {
o[`${i}i`] = i;
}
const json = JSON.stringify(o);
const o1 = JSON.parse(json);
// in-object属性数量越多我们能添加的快速属性就越多
console.log(%DebugPrint(o1));
console.log(%HasFastProperties(o1));
// 执行 node --allow-natives-syntax xx.js
通过测试发现此方法最多可以解析127个属性的快对象并能共享map。
非必要不使用Object.create(null)创建对象
const x = Object.create(null);
console.log(%HasFastProperties(x)); // false
JS对象内存优化的解决方案
小结
参考资料
Fast properties in V8 · V8:https://v8.dev/blog/fast-properties Elements kinds in V8 · V8:https://v8.dev/blog/elements-kinds Explaining JavaScript VMs in JavaScript - Inline Caches:https://mrale.ph/blog/2012/06/03/explaining-js-vms-in-js-inline-caches.html Understanding the size of an object in Chrome/V8:https://www.mattzeunert.com/2017/03/29/v8-object-size.html A tour of V8: object representation:https://jayconrod.com/posts/52/a-tour-of-v8-object-representation