v8的堆内存初始化
- 2019 年 11 月 24 日
- 笔记
在V8::Initialize里对堆进行了初始化
// Setup the object heap ASSERT(!Heap::HasBeenSetup()); if (!Heap::Setup(create_heap_objects)) { has_been_setup_ = false; return false; }
下面来看Setup函数
bool Heap::Setup(bool create_heap_objects) { // Initialize heap spaces and initial maps and objects. Whenever something // goes wrong, just return false. The caller should check the results and // call Heap::TearDown() to release allocated memory. // // If the heap is not yet configured (eg, through the API), configure it. // Configuration is based on the flags new-space-size (really the semispace // size) and old-space-size if set or the initial values of semispace_size_ // and old_generation_size_ otherwise. // 没有配置过则先设置各空间需要的大小 if (!heap_configured) { if (!ConfigureHeap(FLAG_new_space_size, FLAG_old_space_size)) return false; } // Setup memory allocator and allocate an initial chunk of memory. The // initial chunk is double the size of the new space to ensure that we can // find a pair of semispaces that are contiguous and aligned to their size. // 初始化内存分配器的属性,大小等于新生代和老生代大小。还没分配内存 if (!MemoryAllocator::Setup(MaxCapacity())) return false; // 分配一块内存 void* chunk = MemoryAllocator::ReserveInitialChunk(2 * young_generation_size_); if (chunk == NULL) return false; // Put the initial chunk of the old space at the start of the initial // chunk, then the two new space semispaces, then the initial chunk of // code space. Align the pair of semispaces to their size, which must be // a power of 2. ASSERT(IsPowerOf2(young_generation_size_)); // 刚才分配的空间中,老生代在开始位置 Address old_space_start = reinterpret_cast<Address>(chunk); // 紧接着是新生代,算出大小是young_generation_size_的n倍,值大于old_space_start的最小值 Address new_space_start = RoundUp(old_space_start, young_generation_size_); // 代码空间等于新生代开始+新生代大小 Address code_space_start = new_space_start + young_generation_size_; // 老生代空间大小 int old_space_size = new_space_start - old_space_start; /* 因为chunk的空间两倍的young_generation_size_,新生代大小占了一半, 所以还有一半,剩下的一半老生代占了old_space_size,所以剩下的代码区大小 */ int code_space_size = young_generation_size_ - old_space_size; /* |young_generation_size_| chunk => ----------------------------------- ^ ^ ^ ^ | | | | old new code end */ // Initialize new space. // 分配一个管理新生代地址空间的对象,传入初始值和最大值,因为新生代分配from和to,所以这两个初始化值是每个空间的属性 new_space_ = new NewSpace(initial_semispace_size_, semispace_size_); if (new_space_ == NULL) return false; // 设置新生代对象管理的地址范围,young_generation_size_ = 2 * semispace_size_ if (!new_space_->Setup(new_space_start, young_generation_size_)) return false; // Initialize old space, set the maximum capacity to the old generation // size. old_space_ = new OldSpace(old_generation_size_, OLD_SPACE); if (old_space_ == NULL) return false; if (!old_space_->Setup(old_space_start, old_space_size)) return false; // Initialize the code space, set its maximum capacity to the old // generation size. code_space_ = new OldSpace(old_generation_size_, CODE_SPACE); if (code_space_ == NULL) return false; if (!code_space_->Setup(code_space_start, code_space_size)) return false; // Initialize map space. // 存储map的空间 map_space_ = new MapSpace(kMaxMapSpaceSize); if (map_space_ == NULL) return false; // Setting up a paged space without giving it a virtual memory range big // enough to hold at least a page will cause it to allocate. // 在SetUp里分配内存,并初始化管理内存的对象 if (!map_space_->Setup(NULL, 0)) return false; lo_space_ = new LargeObjectSpace(); if (lo_space_ == NULL) return false; if (!lo_space_->Setup()) return false; if (create_heap_objects) { // Create initial maps. if (!CreateInitialMaps()) return false; if (!CreateApiObjects()) return false; // Create initial objects if (!CreateInitialObjects()) return false; } LOG(IntEvent("heap-capacity", Capacity())); LOG(IntEvent("heap-available", Available())); return true; }
我们知道v8的堆是分为新生代,老生代,大对象等区域,从代码中我们也看到内存是分为几个部分,我们一个个来看。首先看NewSpace。
NewSpace::NewSpace(int initial_semispace_capacity, int maximum_semispace_capacity) { ASSERT(initial_semispace_capacity <= maximum_semispace_capacity); ASSERT(IsPowerOf2(maximum_semispace_capacity)); maximum_capacity_ = maximum_semispace_capacity; capacity_ = initial_semispace_capacity; to_space_ = new SemiSpace(capacity_, maximum_capacity_); from_space_ = new SemiSpace(capacity_, maximum_capacity_); // Allocate and setup the histogram arrays if necessary. #if defined(DEBUG) || defined(ENABLE_LOGGING_AND_PROFILING) allocated_histogram_ = NewArray<HistogramInfo>(LAST_TYPE + 1); promoted_histogram_ = NewArray<HistogramInfo>(LAST_TYPE + 1); #define SET_NAME(name) allocated_histogram_[name].set_name(#name); promoted_histogram_[name].set_name(#name); INSTANCE_TYPE_LIST(SET_NAME) #undef SET_NAME #endif }
NewSpace的主要代码是新建了两个SemiSpace。一个是to一个是from,这就是我们经常听说的,新生代对象在from和to区域互相转移。我们再看看SemiSpace。
SemiSpace::SemiSpace(int initial_capacity, int maximum_capacity) : capacity_(initial_capacity), maximum_capacity_(maximum_capacity), start_(NULL), age_mark_(NULL) { }
只是初始化了一些属性。回到最开始,new了一个NewSpace后,执行了该对象的Setup函数。
// 设置需要管理的地址空间,start是首地址,size是大小 bool NewSpace::Setup(Address start, int size) { ASSERT(size == 2 * maximum_capacity_); ASSERT(IsAddressAligned(start, size, 0)); // to区 if (to_space_ == NULL || !to_space_->Setup(start, maximum_capacity_)) { return false; } // from区,和to区一人一半 if (from_space_ == NULL || !from_space_->Setup(start + maximum_capacity_, maximum_capacity_)) { return false; } // 开始地址 start_ = start; /* address_mask的高位是地址的有效位, size是只有一位为一,减一后一变成0,一右边 的全部0位变成1,然后取反,高位的0变成1,再加上size中本来的1, 即从左往右的1位地址有效位 */ address_mask_ = ~(size - 1); object_mask_ = address_mask_ | kHeapObjectTag; object_expected_ = reinterpret_cast<uint32_t>(start) | kHeapObjectTag; // 初始化管理的地址的信息 allocation_info_.top = to_space_->low(); allocation_info_.limit = to_space_->high(); mc_forwarding_info_.top = NULL; mc_forwarding_info_.limit = NULL; ASSERT_SEMISPACE_ALLOCATION_INFO(allocation_info_, to_space_); return true; }
接着来看oldSpace。老生代是新生代对象晋升的地方。
class OldSpace : public PagedSpace { public: // Creates an old space object with a given maximum capacity. // The constructor does not allocate pages from OS. explicit OldSpace(int max_capacity, AllocationSpace id) : PagedSpace(max_capacity, id), free_list_(id) { } ... }
PagedSpace::PagedSpace(int max_capacity, AllocationSpace id) { ASSERT(id == OLD_SPACE || id == CODE_SPACE || id == MAP_SPACE); // 先算出小于max_capacity,是页大小的倍数的最大值,再除以页大小则得到页数,再乘以对象的大小则得到总大小 max_capacity_ = (RoundDown(max_capacity, Page::kPageSize) / Page::kPageSize) * Page::kObjectAreaSize; identity_ = id; accounting_stats_.Clear(); allocation_mode_ = LINEAR; allocation_info_.top = NULL; allocation_info_.limit = NULL; mc_forwarding_info_.top = NULL; mc_forwarding_info_.limit = NULL; }
OldSpace也是初始化了一些字段。然后执行Setup。
bool PagedSpace::Setup(Address start, size_t size) { if (HasBeenSetup()) return false; int num_pages = 0; // Try to use the virtual memory range passed to us. If it is too small to // contain at least one page, ignore it and allocate instead. // 分配虚拟内存,算出有效的大小 if (PagesInChunk(start, size) > 0) { // 分配虚拟内存 first_page_ = MemoryAllocator::CommitPages(start, size, this, &num_pages); } else { int requested_pages = Min(MemoryAllocator::kPagesPerChunk, max_capacity_ / Page::kObjectAreaSize); // 分配虚拟内存 first_page_ = MemoryAllocator::AllocatePages(requested_pages, &num_pages, this); if (!first_page_->is_valid()) return false; } // We are sure that the first page is valid and that we have at least one // page. ASSERT(first_page_->is_valid()); ASSERT(num_pages > 0); accounting_stats_.ExpandSpace(num_pages * Page::kObjectAreaSize); ASSERT(Capacity() <= max_capacity_); // 初始化page链表 for (Page* p = first_page_; p->is_valid(); p = p->next_page()) { p->ClearRSet(); } // Use first_page_ for allocation. SetAllocationInfo(&allocation_info_, first_page_); return true; }
MapSpace和老生代的逻辑一样。最后新建了一个LargeObjectSpace对象。并执行Setup
LargeObjectSpace::LargeObjectSpace() : first_chunk_(NULL), size_(0), page_count_(0) {} bool LargeObjectSpace::Setup() { first_chunk_ = NULL; size_ = 0; page_count_ = 0; return true; }
最后我们来看MemoryAllocator类。首先执行了Setup。
bool MemoryAllocator::Setup(int capacity) { // 页的整数倍 capacity_ = RoundUp(capacity, Page::kPageSize); // Over-estimate the size of chunks_ array. It assumes the expansion of old // space is always in the unit of a chunk (kChunkSize) except the last // expansion. // // Due to alignment, allocated space might be one page less than required // number (kPagesPerChunk) of pages for old spaces. // // Reserve two chunk ids for semispaces, one for map space and one for old // space. // 最大的chunk数, max_nof_chunks_ = (capacity_ / (kChunkSize - Page::kPageSize)) + 4; if (max_nof_chunks_ > kMaxNofChunks) return false; size_ = 0; ChunkInfo info; // uninitialized element. // 初始化chunks列表和id,max_nof_chunks_大于list的长度的话list会自动扩容 for (int i = max_nof_chunks_ - 1; i >= 0; i--) { chunks_.Add(info); free_chunk_ids_.Add(i); } top_ = max_nof_chunks_; return true; }
chunks_和free_chunk_ids_是MemoryAllocator的属性。List类之前分析过。
// Chunks_, free_chunk_ids_ and top_ act as a stack of free chunk ids. static List<ChunkInfo> chunks_; static List<int> free_chunk_ids_; static int max_nof_chunks_; static int top_;
chunkInfo是一个类
class ChunkInfo BASE_EMBEDDED { public: ChunkInfo() : address_(NULL), size_(0), owner_(NULL) {} void init(Address a, size_t s, PagedSpace* o) { address_ = a; size_ = s; owner_ = o; } Address address() { return address_; } size_t size() { return size_; } PagedSpace* owner() { return owner_; } private: Address address_; size_t size_; PagedSpace* owner_; };
执行完Setup接着执行了ReserveInitialChunk函数。
void* MemoryAllocator::ReserveInitialChunk(const size_t requested) { ASSERT(initial_chunk_ == NULL); // 新建一个VM对象,分配size的虚拟内存,记录在VM对象 initial_chunk_ = new VirtualMemory(requested); CHECK(initial_chunk_ != NULL); //是否已经分配了虚拟地址 if (!initial_chunk_->IsReserved()) { delete initial_chunk_; initial_chunk_ = NULL; return NULL; } // We are sure that we have mapped a block of requested addresses. ASSERT(initial_chunk_->size() == requested); LOG(NewEvent("InitialChunk", initial_chunk_->address(), requested)); size_ += requested; // 返回虚拟地址 return initial_chunk_->address(); }
主要逻辑是新建了一个VM对象。VM类定义是
VirtualMemory::VirtualMemory(size_t size, void* address_hint) { address_ = mmap(address_hint, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, kMmapFd, kMmapFdOffset); size_ = size; }
所以一个VM类是管理一片虚拟内存的对象。ReserveInitialChunk函数最后返回分配的虚拟内存首地址。这块内存就是V8的堆内存,即新生代、老生代、大对象等堆内存都在上面。