javascript
索引超出数组界限是什么意思_从V8源码分析一个JS 数组的内存占用问题
前段時間,在排查一個問題的時候,遇到了一個有點令人困惑的情況,有下面這兩段代碼:
const a = new Array(99999); a[99998] = undefined;const b = new Array(99999); b[99999] = undefined;我們通過 node --inspect-brk 來分別運行這兩段代碼,在代碼運行的最開始和結(jié)束的時候分別task heap snapshot,分析對應的內(nèi)存占用信息如下:
可以發(fā)現(xiàn)第二段代碼的內(nèi)存占用明顯要小于第一段,那么問題就出現(xiàn)在這個 99999 的越界賦值上面。
在V8代碼(v8/src/objects/js-array.h#L19)中有很明確的標注,數(shù)組有兩種模式,快數(shù)組和慢數(shù)組,在數(shù)組初始化時,默認的存儲方式為快數(shù)組(v8/src/objects/js-objects.h#L317),其內(nèi)存占用是連續(xù)的,而慢數(shù)組會使用HashTable來進行數(shù)據(jù)存儲。 另外數(shù)組會分為壓緊(Packed)的和有洞的(Holey)兩種,例如 ['a', 'b', 'c'] 這樣的數(shù)組長度為3,數(shù)組索引0、1、2均有值,那么就認為是Packed;而對于 ['a',,,'d'] 這樣的數(shù)組,長度為4,但是索引1、2位置并沒有進行初始化賦值,那么就認為是Holey。當數(shù)組出現(xiàn)了較大空洞的時候,內(nèi)存明顯是被浪費了。
V8中對于大型空洞數(shù)組進行了優(yōu)化,在V8博客(https://v8.dev/blog/fast-properties)中進行說明了這一點,對于非常大的Holey數(shù)組來說,FixedArray會造成內(nèi)存浪費,所以會使用字典來節(jié)約內(nèi)存,也就是會使用慢數(shù)組模式。
使用v8-debug分別對最開始的兩段代碼進行調(diào)試:
可以很明顯的看到,第一個數(shù)組為FixedArray,而第二個數(shù)組為Dictionary,那么為什么只有第二個數(shù)組轉(zhuǎn)換為了字典模式呢?
在V8中JSArray是繼承于JSObject的,所以當設置屬性的時候,會依次執(zhí)行 Object::SetProperty 、 Object::AddDataProperty 、 JSObject::AddDataElement 、 ShouldConvertToSlowElements ,回到V8代碼中,ShouldConvertToSlowElements這個方法,它是用來判斷是否將一個數(shù)組轉(zhuǎn)換為慢模式(Dictionary)(v8/src/objects/js-objects-inl.h#L794):
從上面的代碼可以看到,當設置 99998 的時候,索引小于當前容量的時候,返回值為false,也就是不進行轉(zhuǎn)換。 而當設置 99999 這個索引的值的時候,因為超出了原來的FixedArray容量,那么就會進行擴容,擴容的算法(v8/src/objects/js-objects.h#L540)為容量 + 容量 /2 + 16,那么原來 99999 的容量就會擴容放大到 15萬。
然后會執(zhí)行 GetFastElementsUsage 來獲取原來的數(shù)組中非空洞(v8/src/objects/js-objects.cc#L4725)的元素數(shù)量,乘以 kPreferFastElementsSizeFactor(值為3) 與 kEntrySize (值為2) ,與新的容量長度進行對比,如果小于新的容量長度,那么就轉(zhuǎn)換為慢數(shù)組。
最開始的第二段代碼中,非空洞元素數(shù)量為0,計算后的乘積也為0,因此小于15萬的新數(shù)組長度,于是數(shù)組轉(zhuǎn)換為了慢數(shù)組,使用了Dictionary進行數(shù)據(jù)的存儲,從而節(jié)省了大量的內(nèi)存。
總結(jié)
以上是生活随笔為你收集整理的索引超出数组界限是什么意思_从V8源码分析一个JS 数组的内存占用问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 列表页——基于Django框架的天天生鲜
- 下一篇: gradle idea java ssm