前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >js引擎v8源码分析之MemoryAllocator(基于v8 0.1.5)

js引擎v8源码分析之MemoryAllocator(基于v8 0.1.5)

作者头像
theanarkh
发布2020-02-17 11:44:24
1.2K0
发布2020-02-17 11:44:24
举报
文章被收录于专栏:原创分享原创分享原创分享

MemoryAllocator是负责内存的管理和分配的。MemoryAllocator可以直接申请内存,也可以初始化时申请一块内存。然后把这些内存,分成多个chunk,每一个chunk里面多个page。下面是定义。

class MemoryAllocator : public AllStatic {
 public:

  static bool Setup(int max_capacity);

  static void TearDown();

  static void* ReserveInitialChunk(const size_t requested);

  static Page* CommitPages(Address start, size_t size, PagedSpace* owner, int* num_pages);

  static bool CommitBlock(Address start, size_t size);

  static Page* AllocatePages(int requested_pages, int* allocated_pages,
                             PagedSpace* owner);
  static Page* FreePages(Page* p);

  static void* AllocateRawMemory(const size_t requested, size_t* allocated);
  static void FreeRawMemory(void* buf, size_t length);

  static int Available() { return capacity_ < size_ ? 0 : capacity_ - size_; }

  static inline void SetNextPage(Page* prev, Page* next);

  static inline Page* GetNextPage(Page* p);

  static inline bool IsPageInSpace(Page* p, PagedSpace* space);

  static inline PagedSpace* PageOwner(Page* page);
  static Page* FindFirstPageInSameChunk(Page* p);
  static Page* FindLastPageInSameChunk(Page* p);

  static const int kMaxNofChunks = 1 << Page::kPageSizeBits;
  static const int kPagesPerChunk = 64;
  static const int kChunkSize = kPagesPerChunk * Page::kPageSize;

 private:
  static int capacity_;
  static int size_;
  static VirtualMemory* initial_chunk_;

  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_;
  };

  static List<ChunkInfo> chunks_;
  static List<int> free_chunk_ids_;
  static int max_nof_chunks_;
  static int top_;

  static void Push(int free_chunk_id);
  static int Pop();
  static bool OutOfChunkIds() { return top_ == 0; }

  static void DeleteChunk(int chunk_id);

  static inline bool IsValidChunkId(int chunk_id);

  static inline bool IsValidChunk(int chunk_id);

  static inline int GetChunkId(Page* p);

  static Page* InitializePagesInChunk(int chunk_id, int pages_in_chunk, PagedSpace* owner);
};

我们看一下他的实现

int MemoryAllocator::capacity_   = 0;
int MemoryAllocator::size_       = 0;

VirtualMemory* MemoryAllocator::initial_chunk_ = NULL;

// 270 is an estimate based on the static default heap size of a pair of 256K
// semispaces and a 64M old generation.
// list的元素个数
const int kEstimatedNumberOfChunks = 270;
List<MemoryAllocator::ChunkInfo> MemoryAllocator::chunks_(kEstimatedNumberOfChunks);
List<int> MemoryAllocator::free_chunk_ids_(kEstimatedNumberOfChunks);
int MemoryAllocator::max_nof_chunks_ = 0;
int MemoryAllocator::top_ = 0;

// chunnkId管理
void MemoryAllocator::Push(int free_chunk_id) {
  ASSERT(max_nof_chunks_ > 0);
  ASSERT(top_ < max_nof_chunks_);
  free_chunk_ids_[top_++] = free_chunk_id;
}


int MemoryAllocator::Pop() {
  ASSERT(top_ > 0);
  return free_chunk_ids_[--top_];
}

// 初始化属性
bool MemoryAllocator::Setup(int capacity) {
  // 页的整数倍
  capacity_ = RoundUp(capacity, Page::kPageSize);

  // 最大的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会自动扩容,ChunkId大的在后面,小的id先被Pop出来使用
  for (int i = max_nof_chunks_ - 1; i >= 0; i--) {
    chunks_.Add(info);
    free_chunk_ids_.Add(i);
  }
  top_ = max_nof_chunks_;
  return true;
}


void MemoryAllocator::TearDown() {
  // chunk非空则释放对应的内存
  for (int i = 0; i < max_nof_chunks_; i++) {
    if (chunks_[i].address() != NULL) DeleteChunk(i);
  }
  // 清空list
  chunks_.Clear();
  free_chunk_ids_.Clear();
  // 释放initial_chunk_对应的内存
  if (initial_chunk_ != NULL) {
    LOG(DeleteEvent("InitialChunk", initial_chunk_->address()));
    delete initial_chunk_;
    initial_chunk_ = NULL;
  }

  ASSERT(top_ == max_nof_chunks_);  // all chunks are free
  // 重置属性
  top_ = 0;
  capacity_ = 0;
  size_ = 0;
  max_nof_chunks_ = 0;
}


// 分配虚拟内存
void* MemoryAllocator::AllocateRawMemory(const size_t requested,
                                         size_t* allocated) {
  // 当前大小 + 请求大小不能大于最大容量
  if (size_ + static_cast<int>(requested) > capacity_) return NULL;
  // 请求分配的大小和实际分配的大小,成功的话返回首地址
  void* mem = OS::Allocate(requested, allocated);
  // 实际分配的大小
  int alloced = *allocated;
  // 记录当前已经分配的内存大小
  size_ += alloced;
  Counters::memory_allocated.Increment(alloced);
  return mem;
}

// 释放虚拟内容
void MemoryAllocator::FreeRawMemory(void* mem, size_t length) {
  OS::Free(mem, length);
  Counters::memory_allocated.Decrement(length);
  size_ -= length;
  ASSERT(size_ >= 0);
}

// v8初始化的时候分配的堆内存
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()) {
    // 失败了,释放VM对象
    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();
}

// 算出有效的大小,在start到start + size中有效的内存大小,等于页数*每页大小
static int PagesInChunk(Address start, size_t size) {
  return (RoundDown(start + size, Page::kPageSize)
          - RoundUp(start, Page::kPageSize)) >> Page::kPageSizeBits;
}

// 之前分配的内存不够用,再分配
Page* MemoryAllocator::AllocatePages(int requested_pages, int* allocated_pages,
                                     PagedSpace* owner) {
  if (requested_pages <= 0) return Page::FromAddress(NULL);
  // 页数 * 每页大小
  size_t chunk_size = requested_pages * Page::kPageSize;

  // 空间不够,只能申请小于请求页数的内存了
  if (size_ + static_cast<int>(chunk_size) > capacity_) {
    // Request as many pages as we can.
    // 还能申请的字节数
    chunk_size = capacity_ - size_;
    // 还能申请多少页
    requested_pages = chunk_size >> Page::kPageSizeBits;
    // 不够一页了,直接返回申请失败
    if (requested_pages <= 0) return Page::FromAddress(NULL);
  }
  // 分配虚拟内存,chunk_size是申请分配的大小和实际分配的大小
  void* chunk = AllocateRawMemory(chunk_size, &chunk_size);
  if (chunk == NULL) return Page::FromAddress(NULL);
  LOG(NewEvent("PagedChunk", chunk, chunk_size));
  // 算出实际分配的页数
  *allocated_pages = PagesInChunk(static_cast<Address>(chunk), chunk_size);
  if (*allocated_pages == 0) {
    FreeRawMemory(chunk, chunk_size);
    LOG(DeleteEvent("PagedChunk", chunk));
    return Page::FromAddress(NULL);
  }
  // 取出一个id
  int chunk_id = Pop();
  // 记录在ChunkInfo中
  chunks_[chunk_id].init(static_cast<Address>(chunk), chunk_size, owner);

  return InitializePagesInChunk(chunk_id, *allocated_pages, owner);
}

// 保存和管理虚拟地址start到start+size的空间,
Page* MemoryAllocator::CommitPages(Address start, size_t size,
                                   PagedSpace* owner, int* num_pages) {
  ASSERT(start != NULL);
  // chunk中的页数
  *num_pages = PagesInChunk(start, size);
  ASSERT(*num_pages > 0);
  ASSERT(initial_chunk_ != NULL);
  ASSERT(initial_chunk_->address() <= start);
  ASSERT(start + size <= reinterpret_cast<Address>(initial_chunk_->address())
                             + initial_chunk_->size());
  // commit,修改内存的属性,使得可用,见platform-linux.ccd的mmap
  if (!initial_chunk_->Commit(start, size)) {
    return Page::FromAddress(NULL);
  }
  Counters::memory_allocated.Increment(size);

  // So long as we correctly overestimated the number of chunks we should not
  // run out of chunk ids.
  CHECK(!OutOfChunkIds());
  // 保存信息到chunkInfo,初始化chunk里的page
  int chunk_id = Pop();
  chunks_[chunk_id].init(start, size, owner);
  return InitializePagesInChunk(chunk_id, *num_pages, owner);
}


bool MemoryAllocator::CommitBlock(Address start, size_t size) {
  ASSERT(start != NULL);
  ASSERT(size > 0);
  ASSERT(initial_chunk_ != NULL);
  ASSERT(initial_chunk_->address() <= start);
  ASSERT(start + size <= reinterpret_cast<Address>(initial_chunk_->address())
                             + initial_chunk_->size());
  // commit,修改内存的属性,使得可用,见platform-linux.ccd的mmap
  if (!initial_chunk_->Commit(start, size)) return false;
  Counters::memory_allocated.Increment(size);
  return true;
}

// 初始化某块内存为page管理的结构
Page* MemoryAllocator::InitializePagesInChunk(int chunk_id, int pages_in_chunk,
                                              PagedSpace* owner) {
  ASSERT(IsValidChunk(chunk_id));
  ASSERT(pages_in_chunk > 0);

  Address chunk_start = chunks_[chunk_id].address();
  // 算出有效的开始地址,即要对齐
  Address low = RoundUp(chunk_start, Page::kPageSize);

#ifdef DEBUG
  size_t chunk_size = chunks_[chunk_id].size();
  Address high = RoundDown(chunk_start + chunk_size, Page::kPageSize);
  ASSERT(pages_in_chunk <=
        ((OffsetFrom(high) - OffsetFrom(low)) / Page::kPageSize));
#endif

  Address page_addr = low;
  for (int i = 0; i < pages_in_chunk; i++) {
    Page* p = Page::FromAddress(page_addr);
    // 保存下一页的地址和当前所属的chunk_id
    p->opaque_header = OffsetFrom(page_addr + Page::kPageSize) | chunk_id;
    // 不是large object
    p->is_normal_page = 1;
    // 指向下一页地址
    page_addr += Page::kPageSize;
  }

  // Set the next page of the last page to 0.
  // page_addr此时执行最后一页的末尾,减去一页大小得到最后一页的起始地址
  Page* last_page = Page::FromAddress(page_addr - Page::kPageSize);
  // 下一页地址为0
  last_page->opaque_header = OffsetFrom(0) | chunk_id;

  return Page::FromAddress(low);
}

// 释放多个chunk的内存
Page* MemoryAllocator::FreePages(Page* p) {
  if (!p->is_valid()) return p;

  // Find the first page in the same chunk as 'p'
  // 找出同chunk中的第一个page
  Page* first_page = FindFirstPageInSameChunk(p);
  Page* page_to_return = Page::FromAddress(NULL);
  // 不是第一个page
  if (p != first_page) {
    // Find the last page in the same chunk as 'prev'.
    Page* last_page = FindLastPageInSameChunk(p);
    // 执行p所在chunk的下一个chunk
    first_page = GetNextPage(last_page);  // first page in next chunk

    // set the next_page of last_page to NULL
    SetNextPage(last_page, Page::FromAddress(NULL));
    // p所在的chunk没有被删除
    page_to_return = p;  // return 'p' when exiting
  }
  // 删除多个chunk
  while (first_page->is_valid()) {
    int chunk_id = GetChunkId(first_page);
    ASSERT(IsValidChunk(chunk_id));

    // Find the first page of the next chunk before deleting this chunk.
    first_page = GetNextPage(FindLastPageInSameChunk(first_page));

    // Free the current chunk.
    DeleteChunk(chunk_id);
  }

  return page_to_return;
}

// 删除一个chunk
void MemoryAllocator::DeleteChunk(int chunk_id) {
  ASSERT(IsValidChunk(chunk_id));

  ChunkInfo& c = chunks_[chunk_id];

  bool in_initial_chunk = false;
  // initial_chunk_非空说明之前通过ReserveInitialChunk申请了内存 
  if (initial_chunk_ != NULL) {
    Address start = static_cast<Address>(initial_chunk_->address());
    Address end = start + initial_chunk_->size();
    // 判断当前删除的chunk管理的内存范围是不是在初始化时申请的内存里
    in_initial_chunk = (start <= c.address()) && (c.address() < end);
  }
  // 释放的是初始化申请的内存的一部分,否则是通过malloc额外申请的
  if (in_initial_chunk) {
    // TODO(1240712): VirtualMemory::Uncommit has a return value which
    // is ignored here.
    // 撤销chunk对应的内存,可能导致把之前申请的一大块内存切开。由操作系统完成
    initial_chunk_->Uncommit(c.address(), c.size());
    Counters::memory_allocated.Decrement(c.size());
  } else {
    LOG(DeleteEvent("PagedChunk", c.address()));
    // malloc申请的直接释放就行
    FreeRawMemory(c.address(), c.size());
  }
  // 重置
  c.init(NULL, 0, NULL);
  // 回收chunkId
  Push(chunk_id);
}

// 找出p对应的chunk中的第一个page的地址
Page* MemoryAllocator::FindFirstPageInSameChunk(Page* p) {
  int chunk_id = GetChunkId(p);
  ASSERT(IsValidChunk(chunk_id));

  Address low = RoundUp(chunks_[chunk_id].address(), Page::kPageSize);
  return Page::FromAddress(low);
}

// 找出chunk中的最后一个page的地址
Page* MemoryAllocator::FindLastPageInSameChunk(Page* p) {
  // 根据page获取所在chunk的id
  int chunk_id = GetChunkId(p);
  ASSERT(IsValidChunk(chunk_id));
  // chunk管理的内存的首地址和大小
  Address chunk_start = chunks_[chunk_id].address();
  size_t chunk_size = chunks_[chunk_id].size();
  // chunk管理的内存有效的末地址,即满足对齐的
  Address high = RoundDown(chunk_start + chunk_size, Page::kPageSize);
  ASSERT(chunk_start <= p->address() && p->address() < high);
  // 末地址减一页即最后一页的地址
  return Page::FromAddress(high - Page::kPageSize);
}

// 检查chunk是否管理着有效内存
bool MemoryAllocator::IsValidChunk(int chunk_id) {
  if (!IsValidChunkId(chunk_id)) return false;

  ChunkInfo& c = chunks_[chunk_id];
  return (c.address() != NULL) && (c.size() != 0) && (c.owner() != NULL);
}

// 检查chunkid的有效性
bool MemoryAllocator::IsValidChunkId(int chunk_id) {
  return (0 <= chunk_id) && (chunk_id < max_nof_chunks_);
}

// 检查给定page的地址是否在chunk管理的内存中
bool MemoryAllocator::IsPageInSpace(Page* p, PagedSpace* space) {
  ASSERT(p->is_valid());

  int chunk_id = GetChunkId(p);
  if (!IsValidChunkId(chunk_id)) return false;

  ChunkInfo& c = chunks_[chunk_id];
  return (c.address() <= p->address()) &&
         (p->address() < c.address() + c.size()) &&
         (space == c.owner());
}

// 获取下一页的地址
Page* MemoryAllocator::GetNextPage(Page* p) {
  ASSERT(p->is_valid());
  // 取出在opaque_header中的有效地址,取高位的值
  int raw_addr = p->opaque_header & ~Page::kPageAlignmentMask; // 2 ^ 13 - 1 
  return Page::FromAddress(AddressFrom<Address>(raw_addr));
}

// 取opaque_header的低n位,是ChunkId
int MemoryAllocator::GetChunkId(Page* p) {
  ASSERT(p->is_valid());
  return p->opaque_header & Page::kPageAlignmentMask;
}

// 在prev后插入next
void MemoryAllocator::SetNextPage(Page* prev, Page* next) {
  ASSERT(prev->is_valid());
  int chunk_id = prev->opaque_header & Page::kPageAlignmentMask;
  ASSERT_PAGE_ALIGNED(next->address());
  prev->opaque_header = OffsetFrom(next->address()) | chunk_id;
}

// 获取page所属的Space
PagedSpace* MemoryAllocator::PageOwner(Page* page) {
  int chunk_id = GetChunkId(page);
  ASSERT(IsValidChunk(chunk_id));
  return chunks_[chunk_id].owner();
}

在这里插入图片描述

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-02-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 编程杂技 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档