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的堆記憶體,即新生代、老生代、大對象等堆記憶體都在上面。