|
高效的程序離不開內存的有效管理。自己對內存管理的好處不少:減少內存分配、回收開銷、避免內存碎片、定位內存位置、方便內存整理、跟蹤內存使用等等。V8 的堆內存Heap用于存預編譯的code、JS對象內存分配、運行上下文對象分配、垃圾回收等。
一、內存的建構(Heap::Setup) 1、在V8完成OS操作的setup后,隨即建立和管理內存;首先配置Heap參數(shù),Heap分為Young Generaion & Old Generation,Young Generation被劃分為兩個semispace,每個semispace大小默認為2MB;Old Gernation 默認大小為512MB;概念上講Young Generation = new space,Old Generation = old space,即Heap又可以看成被劃分成若干個space(既有free space,也有PagedSpace,即空間的大小與內存頁大小對齊),每個space專門負責相應對象的內存分配和回收。 2、Heap 接著配置內存分配器MemoryAllocator(singleton),讓之為heap生成ChunkInfo信息,其中包括了內存地址、大小以及所屬的PagedSpace。Heap被劃分成若干個chunk,每個chunk大小=PagesPerChunk*PageSize = 64*8K(視操作系統(tǒng)內存頁而定)= 512K。同時內存分配器預留分配了一塊2*Young Generation大?。?/span>8MB)的虛擬內存(initial_chunk_)。 后續(xù)所有內存的分配都得通過MemoryAllocator進行,setup時候設置了最大的size為Young Generation Size + Old Generation Size,即默認為516MB。 3、接著Heap對該塊虛擬內存進行如下空間劃分:
4、每個space的setup。 (4.1)各個space的關系如下:
A. LargeObjectSpace :為了避免大對象的拷貝,使用該空間專門存儲大對象(大小超過Normal Page能容納的對象范圍),包括Code、Sequetial String、FixedArray; B. MapSpace :存放對象的Map信息,即hidden_class;最大限制為8MB;每個Map對象固定大小,為了快速定位,所以將該空間單獨出來; C. NewSpace :存放多種類型對象,最大限制為2MB; D. CodeSpace :存放預編譯代碼(?);最大限制為512MB; E. Old_Pointer_Space :存放GC后surviving的指針對象;最大限制為512MB; F. Old_Data_Space :存放GC后surviving的數(shù)據(jù)對象;最大限制為512MB;
二、內存的析構(Heap::TearDown) new_space_、old_pointer_space_、old_data_space_、code_space_、map_space_、lo_space_依次析構,最后是內存分配器MemoryAllocator::TearDown。
三、空間的內存分配 根據(jù)對象類型、對象生命狀態(tài),在對應的空間中分配內存,總的內存申請入口在Heap::AllocateRaw。每個space的相同之處,都使用AllocationInfo記錄空間的top和limit,即當前可用內存起始地址和終止地址。但不同space的內存分配邏輯不同,具體表現(xiàn)為: 1、 PagedSpace的內存分配 由于PagedSpace空間由多個page組成,page的結構如下:
為了避免申請的內存跨頁(即避免內存缺頁中斷,從而增加對象訪問延遲),所以分配內存步驟可以劃分為: (1)如果申請內存大小沒有超過current_page限制,則直接劃分出該大小區(qū)域,并往前移動top指針(增加地址); (2)通過current_page的opaque_header查找到nextpage,并詢問其是否可用,是則把AllocationInfo更新為nextpage,返回(1);此外,對于current_page剩余的內存塊,不同的派生類處理不同,MapSpace直接將之丟棄,可能是考慮到map大小固定,每個page浪費掉PageSize%MapSize,空間不算大,不過它仍然有free list,收集map對象回收后的內存;OldSpace則將之放入到一個free list,供后面使用; (3)從空間的free list查找是否有可用的內存塊,MapSpace free list直接把頭結點劃分出去,而OldSpaceFreeList::Allocate復雜些,后續(xù)討論; (4)擴展空間,MemoryAllocator::AllocatePages轉而請求分配虛擬內存,在不超過最大空間限制前提下擴展一個chunk,即64個page,返回(2); (5)返回Failure::RetryAfterGC,回收內存垃圾并重試。后續(xù)會解釋如果利用該返回結果進行GC并重試。 具體流程如下:
從PagedSpace::Expand可以窺見該space的初始化邏輯如下: a)指定需要分配的Page數(shù)目,交給內存配置器MemoryAllocator執(zhí)行
b)MemoryAllocator自身也有最大內存數(shù)目的限制,不能保證剛才指定的page數(shù)目,只有最大限度地滿足,不然只有返回錯誤;
c)初始化chunk中的所有Page信息;
3、LargeObjectSpace的內存分配 由于該空間中每個Page都只會存放一個對象,所以當申請內存塊時,直接通過MemoryAllocator::AllocateRawMemory分出一塊對象大小的內存,并加入到該空間的內存塊管理鏈表中。
|
|
|