V8 新生代垃圾回收的实现

2022-05-16 15:56:37 浏览数 (3)

前言:因为最近在做一些 gc track 的事情,所以打算了解一下 V8 GC 的实现。介绍 V8 GC 的文章网上已经有很多,就不打算再重复介绍。本文主要介绍一下新生代 GC 的实现,代码参考 V8 10.2,因为 GC 的实现非常复杂,只能介绍一些大致的实现,读者需要对 V8 GC 有一定的了解,比如新生代是分为 from 和 to 两个 space,然后在 GC 时是如何处理的。

说到 GC 首先需要介绍内存,具体来说,是堆内存,V8 把内存分为新生代和老生代,其中老生代又分为很多种类型,不过本文只关注新生代。下面先来看一下在 V8 初始化的过程中,涉及到新生代的部分,具体逻辑在 Heap::SetUpSpaces 函数。

代码语言:javascript复制
void Heap::SetUpSpaces(...) {
    // 分配内存
    space_[NEW_SPACE] = new_space_ = new NewSpace(
          this, 
          memory_allocator_->data_page_allocator(), 
          initial_semispace_size_,
          max_semi_space_size_,
          new_allocation_info);
    // 初始化 GC 调度对象
    scavenge_job_.reset(new ScavengeJob());
    scavenge_task_observer_.reset(
        new ScavengeTaskObserver(
        this, 
        ScavengeJob::YoungGenerationTaskTriggerSize(this))
    );
    new_space()->AddAllocationObserver(scavenge_task_observer_.get());
    // 初始化 GC 收集器
    scavenger_collector_.reset(new ScavengerCollector(this));
}

在 V8 的堆中,通过 new_space_ 字段记录了新生代的堆内存对象,另外还有几个和 GC 相关的逻辑,scavenge_job_ 和 scavenge_task_observer_ 是处理 GC 对象,下面来逐个分析下。

1 分配内存

代码语言:javascript复制
NewSpace::NewSpace(Heap* heap, v8::PageAllocator* page_allocator,
                   size_t initial_semispace_capacity,
                   size_t max_semispace_capacity,
                   LinearAllocationArea* allocation_info) ...,
      to_space_(heap, kToSpace),
      from_space_(heap, kFromSpace) {

  to_space_.SetUp(initial_semispace_capacity, max_semispace_capacity);
  from_space_.SetUp(initial_semispace_capacity, max_semispace_capacity);
  to_space_.Commit();
}

NewSpace 中初始化了 from 和 to 两个 space。from 和 to 两个 space 是用 SemiSpace 表示。看一下它的 SetUp 方法。

代码语言:javascript复制
void SemiSpace::SetUp(size_t initial_capacity, size_t maximum_capacity) {
  minimum_capacity_ = RoundDown(initial_capacity, Page::kPageSize);
  target_capacity_ = minimum_capacity_;
  maximum_capacity_ = RoundDown(maximum_capacity, Page::kPageSize);
}

SetUp 初始化了该 space 的内存大小字段,但是还没有分配内存。SetUp 执行完之后接着调了 to space 的 Commit 的方法(没有调 from space 的 Commit 方法,根据 V8 的注释,因为 from space 是在 GC 时才需要的,这里大概是用了懒初始化)。接着看 Commit。

代码语言:javascript复制
bool SemiSpace::Commit() {
  // 计算需要多少 Page
  const int num_pages = static_cast<int>(target_capacity_ / Page::kPageSize);
  for (int pages_added = 0; pages_added < num_pages; pages_added  ) {
    // 分配 Page
    Page* new_page = heap()->memory_allocator()->AllocatePage(
        MemoryAllocator::AllocationMode::kUsePool, this, NOT_EXECUTABLE);
    // 保存起来
    memory_chunk_list_.PushBack(new_page);
  }
}

Commit 根据需要的内存计算出 Page 数,然后分配内存,Page 是内存管理的单位,一块内存是由多个 Page 组成的。至此,新生代的内存分配完毕。

2 GC 处理

首先看一下 ScavengeJob。ScavengeJob 是管理 GC 调度的。

代码语言:javascript复制
class ScavengeJob {
 public:
  ScavengeJob() V8_NOEXCEPT = default;
  // 判断是否需要发起 GC
  void ScheduleTaskIfNeeded(Heap* heap);
  // 发起 GC 的阈值
  static size_t YoungGenerationTaskTriggerSize(Heap* heap);

 private:
  class Task;
  // 判断内存是否达到了阈值
  static bool YoungGenerationSizeTaskTriggerReached(Heap* heap);

  void set_task_pending(bool value) { task_pending_ = value; }

  bool task_pending_ = false;
};

ScavengeJob 记录了内存达到多少时需要发起 GC,并实现了发起 GC 的逻辑。我们先看一下阈值。

代码语言:javascript复制
size_t ScavengeJob::YoungGenerationTaskTriggerSize(Heap* heap) {
  // FLAG_scavenge_task_trigger = 80
  return heap->new_space()->Capacity() * FLAG_scavenge_task_trigger / 100;
}

bool ScavengeJob::YoungGenerationSizeTaskTriggerReached(Heap* heap) {
  return heap->new_space()->Size() >= YoungGenerationTaskTriggerSize(heap);
}

V8 默认逻辑是内存达到 80% 时触发 GC,可以通过 scavenge_task_trigger flag 进行控制。V8 会调用 ScheduleTaskIfNeeded 判断是否需要发起 GC。

代码语言:javascript复制
void ScavengeJob::ScheduleTaskIfNeeded(Heap* heap) {
  if (FLAG_scavenge_task && !task_pending_ && !heap->IsTearingDown() &&
      YoungGenerationSizeTaskTriggerReached(heap)) {
    v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(heap->isolate());
    auto taskrunner = V8::GetCurrentPlatform()->GetForegroundTaskRunner(isolate);
    if (taskrunner->NonNestableTasksEnabled()) {
      taskrunner->PostNonNestableTask(
          std::make_unique<Task>(heap->isolate(), this)
      );
      task_pending_ = true;
    }
  }
}

ScheduleTaskIfNeeded 首先判断内存是否达到了阈值,是的就给线程池提交一个 GC 人物。V8 中有一个 platform 的概念,比如在 Node.js 里是 NodePlatform,这个对象内部有一个线程池,V8 会把 GC 任务提交到线程池中等待处理。一个 GC 任务由 Task 对象表示。

代码语言:javascript复制
class ScavengeJob::Task : public CancelableTask {
 public:
  Task(Isolate* isolate, ScavengeJob* job)
      : CancelableTask(isolate), isolate_(isolate), job_(job) {}

  // CancelableTask overrides.
  void RunInternal() override;

  Isolate* isolate() const { return isolate_; }

 private:
  Isolate* const isolate_;
  ScavengeJob* const job_;
};

Task 继承了 CancelableTask,并且内部有一个 ScavengeJob 对象。

代码语言:javascript复制
class V8_EXPORT_PRIVATE CancelableTask : public Cancelable,
                                         NON_EXPORTED_BASE(public Task) {
 public:
  // Task overrides.
  void Run() final {
    if (TryRun()) {
      RunInternal();
    }
  }

  virtual void RunInternal() = 0;
};

当任务给线程池调度执行时,CancelableTask 的 Run 函数会被执行,从而执行 RunInternal 函数,该函数由子类实现。接着看 ScavengeJob::Task 中关于这个函数的实现。

代码语言:javascript复制
void ScavengeJob::Task::RunInternal() {
  VMState<GC> state(isolate());
  if (ScavengeJob::YoungGenerationSizeTaskTriggerReached(isolate()->heap())) {
    isolate()->heap()->CollectGarbage(NEW_SPACE,
                                      GarbageCollectionReason::kTask);
  }

  job_->set_task_pending(false);
}

这里再次进行了内存是否达到阈值的判断,如果达到了就直接进行 GC,下面看 CollectGarbage。

代码语言:javascript复制
bool Heap::CollectGarbage(AllocationSpace space,
                          GarbageCollectionReason gc_reason,
                          const v8::GCCallbackFlags gc_callback_flags) {

  const char* collector_reason = nullptr;
  // 根据 space 选择 GC 回收器类型,这里是新生代,选择的是 SCAVENGER,具体可以参考 SelectGarbageCollector 逻辑
  GarbageCollector collector = SelectGarbageCollector(space, &collector_reason);
  // 根据 GC 回收器类型选择 GC 类型,这个就是我们在 gc track 时拿到的类型
  GCType gc_type = GetGCTypeFromGarbageCollector(collector);

  {
    GCCallbacksScope scope(this);
    // 执行“开始 GC” 回调,我们注册的 GC track 回调在这里被执行
    if (scope.CheckReenter()) {
      CallGCPrologueCallbacks(gc_type, kNoGCCallbackFlags);
    }
  }
  // 执行 GC
  PerformGarbageCollection(collector, gc_reason, collector_reason, gc_callback_flags);
  // 执行 “GC 执行完”回调
  {
    GCCallbacksScope scope(this);
    if (scope.CheckReenter()) {
      CallGCEpilogueCallbacks(gc_type, gc_callback_flags);
    }
  }
}

接着看 PerformGarbageCollection。

代码语言:javascript复制
size_t Heap::PerformGarbageCollection(
    GarbageCollector collector, GarbageCollectionReason gc_reason,
    const char* collector_reason, const v8::GCCallbackFlags gc_callback_flags) {

  switch (collector) {
    case GarbageCollector::MARK_COMPACTOR:
      MarkCompact();
      break;
    case GarbageCollector::MINOR_MARK_COMPACTOR:
      MinorMarkCompact();
      break;
    case GarbageCollector::SCAVENGER:
      Scavenge();
      break;
  }
}

继续调用 Scavenge。

代码语言:javascript复制
void Heap::Scavenge() {
  // 进行 from space 和 to space 的翻转
  new_space()->Flip();
  new_space()->ResetLinearAllocationArea();

  // We also flip the young generation large object space. All large objects
  // will be in the from space.
  new_lo_space()->Flip();
  new_lo_space()->ResetPendingObject();

  // Implements Cheney's copying algorithm
  scavenger_collector_->CollectGarbage();

}

Scavenge 是真正执行 GC 的地方,首先第一步进行 from space 和 to space 的翻转,然后执行 GC。我们看看翻转的逻辑。

代码语言:javascript复制
void NewSpace::Flip() { SemiSpace::Swap(&from_space_, &to_space_); }

void SemiSpace::Swap(SemiSpace* from, SemiSpace* to) {

  auto saved_to_space_flags = to->current_page()->GetFlags();

  // We swap all properties but id_.
  std::swap(from->target_capacity_, to->target_capacity_);
  std::swap(from->maximum_capacity_, to->maximum_capacity_);
  std::swap(from->minimum_capacity_, to->minimum_capacity_);
  std::swap(from->age_mark_, to->age_mark_);
  std::swap(from->memory_chunk_list_, to->memory_chunk_list_);
  std::swap(from->current_page_, to->current_page_);
  std::swap(from->external_backing_store_bytes_,
            to->external_backing_store_bytes_);
  std::swap(from->committed_physical_memory_, to->committed_physical_memory_);

  to->FixPagesFlags(saved_to_space_flags, Page::kCopyOnFlipFlagsMask);
  from->FixPagesFlags(Page::NO_FLAGS, Page::NO_FLAGS);
}

这里只是进行了一些字段的交换,真正的逻辑在 GC 收集器中。

代码语言:javascript复制
void ScavengerCollector::CollectGarbage() {
  ScopedFullHeapCrashKey collect_full_heap_dump_if_crash(isolate_);

  std::vector<std::unique_ptr<Scavenger>> scavengers;
  Scavenger::EmptyChunksList empty_chunks;
  // 计算需要提交多少个 GC 任务
  const int num_scavenge_tasks = NumberOfScavengeTasks();
  Scavenger::CopiedList copied_list;
  Scavenger::PromotionList promotion_list;
  EphemeronTableList ephemeron_table_list;

  {
    for (int i = 0; i < num_scavenge_tasks;   i) {
      scavengers.emplace_back(
          new Scavenger(this, heap_, is_logging, &empty_chunks, &copied_list,
                        &promotion_list, &ephemeron_table_list, i));
    }

    // 拿到 heap 中的所有内存块
    std::vector<std::pair<ParallelWorkItem, MemoryChunk*>> memory_chunks;
    RememberedSet<OLD_TO_NEW>::IterateMemoryChunks(
        heap_, [&memory_chunks](MemoryChunk* chunk) {
          memory_chunks.emplace_back(ParallelWorkItem{}, chunk);
        });

    // 遍历堆对象迭代器
    RootScavengeVisitor root_scavenge_visitor(scavengers[kMainThreadId].get());

    {
      // 遍历堆对象
      heap_->IterateRoots(&root_scavenge_visitor, options);
      // 遍历 global handle 对象
      isolate_->global_handles()->IterateYoungStrongAndDependentRoots( &root_scavenge_visitor);
      scavengers[kMainThreadId]->Publish();
    }
    // 提交 GC 任务
    {
      // Parallel phase scavenging all copied and promoted objects.
      V8::GetCurrentPlatform()
          ->PostJob(v8::TaskPriority::kUserBlocking,
                    std::make_unique<JobTask>(this, &scavengers,
                                              std::move(memory_chunks),
                                              &copied_list, &promotion_list))
          ->Join();
    }
  }
  // 回收 ArrayBuffer 内存,比如 Node.js 的 Buffer
  {
    TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SWEEP_ARRAY_BUFFERS);
    SweepArrayBufferExtensions();
  }
}

这里的逻辑非常多,除了回收新生代对象的内存,还会处理 global handle 和 ArrayBuffer 的内存。不过这里我们只关注一般的新生代对象。接着遍历堆对象的过程。

代码语言:javascript复制
void Heap::IterateRoots(RootVisitor* v, base::EnumSet<SkipRoot> options) {
  v->VisitRootPointers(Root::kStrongRootList, nullptr,
                       roots_table().strong_roots_begin(),
                       roots_table().strong_roots_end());
 // ... 省略非常多逻辑
}

Heap 对象提供了迭代的接口,具体迭代逻辑由 Visitor 实现,这里是 RootScavengeVisitor。

代码语言:javascript复制
void RootScavengeVisitor::VisitRootPointer(Root root, const char* description,
                                           FullObjectSlot p) {
  ScavengePointer(p);
}

void RootScavengeVisitor::ScavengePointer(FullObjectSlot p) {
  Object object = *p;
  // 如果是新生代对象则处理
  if (Heap::InYoungGeneration(object)) {
    scavenger_->ScavengeObject(FullHeapObjectSlot(p), HeapObject::cast(object));
  }
}

接着看 ScavengeObject。

代码语言:javascript复制
template <typename THeapObjectSlot>
SlotCallbackResult Scavenger::ScavengeObject(THeapObjectSlot p,
                                             HeapObject object) {
  return EvacuateObject(p, map, object);
}

template <typename THeapObjectSlot>
SlotCallbackResult Scavenger::EvacuateObject(THeapObjectSlot slot, Map map,
                                             HeapObject source) {

  int size = source.SizeFromMap(map);
  // 堆对象的大小
  VisitorId visitor_id = map.visitor_id();
  switch (visitor_id) {
    // ...  省略其他 case
    default:
      return EvacuateObjectDefault(map, slot, source, size,
                                   Map::ObjectFieldsFrom(visitor_id));
  }
}

emplate <typename THeapObjectSlot, Scavenger::PromotionHeapChoice promotion_heap_choice>
SlotCallbackResult Scavenger::EvacuateObjectDefault(
    Map map, THeapObjectSlot slot, HeapObject object, int object_size,
    ObjectFields object_fields) {
  // 是否可以晋升到老生代,新生代对象经过 n 次 GC 还存活则可以晋升到老生代(n = 1)
  if (!heap()->ShouldBePromoted(object.address())) {
    // 不能晋升则移到 from space
    result = SemiSpaceCopyObject(map, slot, object, object_size, object_fields);
  }
  // 否则晋升到老生代
  result = PromoteObject<THeapObjectSlot, promotion_heap_choice>(
      map, slot, object, object_size, object_fields);
  // 晋升失败则 fallback,移到 from 区
  SemiSpaceCopyObject(map, slot, object, object_size, object_fields);
}

接着看 SemiSpaceCopyObject 和 PromoteObject。

代码语言:javascript复制
template <typename THeapObjectSlot>
CopyAndForwardResult Scavenger::SemiSpaceCopyObject(
    Map map, THeapObjectSlot slot, HeapObject object, int object_size,
    ObjectFields object_fields) {

  AllocationAlignment alignment = HeapObject::RequiredAlignment(map);
  // 在 from space 分配一块新的内存,把 to space 的对象移动过去
  AllocationResult allocation = allocator_.Allocate(
      NEW_SPACE, object_size, AllocationOrigin::kGC, alignment);
   // 进行对象迁移
   MigrateObject(map, object, target, object_size, kPromoteIntoLocalHeap);
}

bool Scavenger::MigrateObject(Map map, HeapObject source, HeapObject target,
                              int size,
                              PromotionHeapChoice promotion_heap_choice) {
  // 内存复制,比如通过 memmove
  heap()->CopyBlock(target.address()   kTaggedSize,
                    source.address()   kTaggedSize, size - kTaggedSize);

   // 触发对象移动事件,比如 heap_profiler 回监听这个事件
  if (V8_UNLIKELY(is_logging_)) {
    heap()->OnMoveEvent(target, source, size);
  }
}

至此就完成了对象的迁移。接着看对象的晋升。

代码语言:javascript复制
template <typename THeapObjectSlot,
          Scavenger::PromotionHeapChoice promotion_heap_choice>
CopyAndForwardResult Scavenger::PromoteObject(Map map, THeapObjectSlot slot,
                                              HeapObject object,
                                              int object_size,
                                              ObjectFields object_fields) {
  AllocationAlignment alignment = HeapObject::RequiredAlignment(map);
  AllocationResult allocation;
  // 在老生代分配一块内存
  allocation = allocator_.Allocate(OLD_SPACE, object_size,
                                       AllocationOrigin::kGC, alignment);

  HeapObject target;
  allocation.To(&target);
  // 迁移过去
  MigrateObject(map, object, target, object_size, promotion_heap_choice);
}

对象的晋升本质上也是内存的复制,只不过是复制到了老生代的内存。完成了 from space 和 to space 对象的处理后,还需要另外的任务需要处理。具体由提交给线程池的 JobTask 对象实现。

代码语言:javascript复制
V8::GetCurrentPlatform()
          ->PostJob(v8::TaskPriority::kUserBlocking,
                    std::make_unique<JobTask>(this, &scavengers,
                                              std::move(memory_chunks),
                                              &copied_list, &promotion_list))
          ->Join();

来看一下该对象 Run 的实现。

代码语言:javascript复制
void ScavengerCollector::JobTask::Run(JobDelegate* delegate) {
  Scavenger* scavenger = (*scavengers_)[delegate->GetTaskId()].get();
  ProcessItems(delegate, scavenger);
}

void ScavengerCollector::JobTask::ProcessItems(JobDelegate* delegate,
                                               Scavenger* scavenger) {
  double scavenging_time = 0.0;
  {
    TimedScope scope(&scavenging_time);
    // 并行处理内存
    ConcurrentScavengePages(scavenger);
    scavenger->Process(delegate);
  }
}

void ScavengerCollector::JobTask::ConcurrentScavengePages(
    Scavenger* scavenger) {
  while (remaining_memory_chunks_.load(std::memory_order_relaxed) > 0) {
    base::Optional<size_t> index = generator_.GetNext();
    if (!index) return;
    for (size_t i = *index; i < memory_chunks_.size();   i) {
      auto& work_item = memory_chunks_[i];
      if (!work_item.first.TryAcquire()) break;
      scavenger->ScavengePage(work_item.second);
      if (remaining_memory_chunks_.fetch_sub(1, std::memory_order_relaxed) <=
          1) {
        return;
      }
    }
  }
}

具体看 scavenger->ScavengePage(work_item.second) 。

代码语言:javascript复制
void Scavenger::ScavengePage(MemoryChunk* page) {
  CodePageMemoryModificationScope memory_modification_scope(page);

  if (page->slot_set<OLD_TO_NEW, AccessMode::ATOMIC>() != nullptr) {
    InvalidatedSlotsFilter filter = InvalidatedSlotsFilter::OldToNew(page);
    RememberedSet<OLD_TO_NEW>::IterateAndTrackEmptyBuckets(
        page,
        [this, &filter](MaybeObjectSlot slot) {
          if (!filter.IsValid(slot.address())) return REMOVE_SLOT;
          return CheckAndScavengeObject(heap_, slot);
        },
        &empty_chunks_local_);
  }

  if (page->invalidated_slots<OLD_TO_NEW>() != nullptr) {
    page->ReleaseInvalidatedSlots<OLD_TO_NEW>();
  }

  RememberedSet<OLD_TO_NEW>::IterateTyped(
      page, [=](SlotType type, Address addr) {
        return UpdateTypedSlotHelper::UpdateTypedSlot(
            heap_, type, addr, [this](FullMaybeObjectSlot slot) {
              return CheckAndScavengeObject(heap(), slot);
            });
      });

  AddPageToSweeperIfNecessary(page);
}

大概就是进行了数据的更新和内存的回收。至此,GC 的流程就大致分析完了。

触发 GC

刚出分析了 GC 的处理过程,接下来看看什么时候会触发 GC。相关代码如下。

代码语言:javascript复制
scavenge_task_observer_.reset(new ScavengeTaskObserver(this, ScavengeJob::YoungGenerationTaskTriggerSize(this)));
new_space()->AddAllocationObserver(scavenge_task_observer_.get());

V8 初始化时,给新生代对象注册了一个内存分配的观察者,首先看一下观察者的实现。

代码语言:javascript复制
class ScavengeTaskObserver : public AllocationObserver {
 public:
  ScavengeTaskObserver(Heap* heap, intptr_t step_size)
      : AllocationObserver(step_size), heap_(heap) {}

  void Step(int bytes_allocated, Address, size_t) override {
    heap_->ScheduleScavengeTaskIfNeeded();
  }

 private:
  Heap* heap_;
};

观察者的实现很简单,V8 在分配内存的过程中会执行观察者的 Step 方法,该方法会判断是否需要 GC,下面是 ScheduleScavengeTaskIfNeeded 的实现。

代码语言:javascript复制
void Heap::ScheduleScavengeTaskIfNeeded() {
  scavenge_job_->ScheduleTaskIfNeeded(this);
}

void ScavengeJob::ScheduleTaskIfNeeded(Heap* heap) {
  if (FLAG_scavenge_task && !task_pending_ && !heap->IsTearingDown() &&
      YoungGenerationSizeTaskTriggerReached(heap)) {
    v8::Isolate* isolate = reinterpret_cast<v8::Isolate*>(heap->isolate());
    auto taskrunner =
        V8::GetCurrentPlatform()->GetForegroundTaskRunner(isolate);
    if (taskrunner->NonNestableTasksEnabled()) {
      taskrunner->PostNonNestableTask(
          std::make_unique<Task>(heap->isolate(), this));
      task_pending_ = true;
    }
  }
}

这个过程刚出已经分析过了。接下来再往前看,什么时候会调用 Step。创建完观察者后,会把观察者注册到 newSpace 中。

代码语言:javascript复制
new_space()->AddAllocationObserver(scavenge_task_observer_.get());

看一下 AddAllocationObserver。

代码语言:javascript复制
void Space::AddAllocationObserver(AllocationObserver* observer) {
  allocation_counter_.AddAllocationObserver(observer);
}

那么 allocation_counter_ 又是什么呢?allocation_counter_ 是 AllocationCounter 对象。

代码语言:javascript复制
class AllocationCounter final {
 public:
  AllocationCounter() = default;

  V8_EXPORT_PRIVATE void AddAllocationObserver(AllocationObserver* observer);

  V8_EXPORT_PRIVATE void RemoveAllocationObserver(AllocationObserver* observer);

  V8_EXPORT_PRIVATE void AdvanceAllocationObservers(size_t allocated);

  V8_EXPORT_PRIVATE void InvokeAllocationObservers(Address soon_object,
                                                   size_t object_size,
                                                   size_t aligned_object_size);

 private:
  struct AllocationObserverCounter final {
    AllocationObserverCounter(AllocationObserver* observer, size_t prev_counter,
                              size_t next_counter)
        : observer_(observer),
          prev_counter_(prev_counter),
          next_counter_(next_counter) {}

    AllocationObserver* observer_;
  };

  std::vector<AllocationObserverCounter> observers_;
};

AllocationCounter 里记录了多个 AllocationObserverCounter 对象,而 AllocationObserverCounter 对象封装了 AllocationObserver 对象。来看一下 AddAllocationObserver 方法的实现。

代码语言:javascript复制
void AllocationCounter::AddAllocationObserver(AllocationObserver* observer) {
    observers_.push_back(AllocationObserverCounter(observer, current_counter_,
                                                 observer_next_counter));
}

newSpace 通过 AllocationCounter 管理了多个观察者,接着看调用观察者的时机,也就是分配内存的时候。

代码语言:javascript复制
AllocationResult SpaceWithLinearArea::AllocateRawAligned(
    int size_in_bytes, AllocationAlignment alignment, AllocationOrigin origin) {
  // 分配内存
  AllocationResult result = AllocateFastAligned(
      size_in_bytes, &aligned_size_in_bytes, alignment, origin);
  // 调用观察者
  InvokeAllocationObservers(result.ToAddress(), size_in_bytes,
                            aligned_size_in_bytes, max_aligned_size);

  return result;
}

接着看 InvokeAllocationObservers。

代码语言:javascript复制
void AllocationCounter::InvokeAllocationObservers(Address soon_object,
                                                  size_t object_size,
                                                  size_t aligned_object_size) {

  for (AllocationObserverCounter& aoc : observers_) {
    if (aoc.next_counter_ - current_counter_ <= aligned_object_size) {
      {
        // 执行观察者的 Step 方法,也就是刚出分析 GC 处理时提到的
        aoc.observer_->Step(
            static_cast<int>(current_counter_ - aoc.prev_counter_), soon_object,
            object_size);
      }
    }
  }
}

至此,所有的过程分析完毕。

4 总结

V8 的 GC 经过多年的优化已经变得非常高效,和其他优化技术一起实现了 V8 引擎的高性能。具体的实现非常复杂,涉及的逻辑非常多,时间有限,也就只能大致分析一下,了解基础的原理。

0 人点赞