V8 源码的垃圾回收机制究竟隐藏了哪些奥秘?

284 阅读5分钟

大家好,我是徐徐。今天我们来从 V8 的源码来分析分析浏览器的垃圾回收机制。

前言

最近在读 Chromium 的源码,然后发现了里面的 V8 目录,这不就是我们天天在说的 V8 引擎吗,于是就看了起来。但是里面的东西的确很复杂,就想到关于 V8 的一些问题,最常见的就是垃圾回收机制的解读,于是我就找到了这一块的源码来看,现在开始进入正题。

在现代 Web 应用中,JavaScript 的性能至关重要。而垃圾回收(Garbage Collection,简称GC)作为内存管理的核心机制,直接影响着JavaScript的执行效率。本文将解析V8引擎的源码,剖析其垃圾回收的实现原理,如果你是直接在 GitHub 直接下载的 Chromium 源码里面是没有 V8 的源码的,需要同步所有代码后才能看到,如果想单独看需要在这里看:

github.com/v8/v8

浏览器引擎概述

现代主流浏览器主要使用以下几种JavaScript引擎:

  • V8: 由Google开发,用于Chrome和Node.js
  • SpiderMonkey: Mozilla开发,用于Firefox
  • JavaScriptCore: Apple开发,用于Safari

本文将主要关注V8引擎,因为它不仅应用广泛,而且有丰富的文档和开放的源代码。

垃圾回收基础

在深入V8的实现之前,我们需要了解几种常见的垃圾回收算法:

标记-清除(Mark and Sweep):

  • 标记阶段:从根对象开始,标记所有可达对象
  • 清除阶段:清除所有未标记对象

复制算法(Copying):

  • 将内存分为两个相等的区域
  • 将存活对象从一个区域复制到另一个区域
  • 清空原有区域

分代收集(Generational Collection):

  • 将对象按年龄分类(新生代和老生代)
  • 对不同代的对象采用不同的回收算法

V8中的垃圾回收实现

V8采用了分代回收的策略,将堆内存分为新生代和老生代。

新生代(Young Generation):

  • 使用半空间(Semi-space)设计
  • 采用Scavenge算法进行回收
  • 适用于生命周期短的对象

老生代(Old Generation):

  • 使用标记-清除和标记-整理算法
  • 适用于生命周期长的对象

V8垃圾回收核心代码解析

源码位置:

github.com/v8/v8/blob/…

下面是代码中的几个核心逻辑部分及其简要解析:

ScavengerCollector 类

ScavengerCollector 类负责管理和执行新生代对象的垃圾回收。主要方法包括:

  • CollectGarbage:这是垃圾回收的入口函数,执行垃圾回收的主要步骤,包括标记、复制和处理弱引用。
  • ProcessWeakReferences:处理弱引用,确保在垃圾回收过程中正确处理弱引用对象。
  • ScavengePage:处理内存页中的对象,检查并转移新生代对象。
void ScavengerCollector::CollectGarbage() {
  // 初始化和准备阶段
  auto* new_space = SemiSpaceNewSpace::From(heap_->new_space());
  new_space->GarbageCollectionPrologue();
  new_space->EvacuatePrologue();

  // 翻转新生代大对象空间
  heap_->new_lo_space()->Flip();
  heap_->new_lo_space()->ResetPendingObject();

  // 创建多个 Scavenger 对象用于并行回收
  std::vector<std::unique_ptr<Scavenger>> scavengers;
  Scavenger::EmptyChunksList empty_chunks;
  const int num_scavenge_tasks = NumberOfScavengeTasks();
  Scavenger::CopiedList copied_list;
  Scavenger::PromotionList promotion_list;
  EphemeronRememberedSet::TableList ephemeron_table_list;

  {
    // 初始化 Scavenger 对象
    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));
    }

    // 处理根对象
    RootScavengeVisitor root_scavenge_visitor(scavengers[kMainThreadId].get());
    heap_->IterateRoots(&root_scavenge_visitor, options);
    isolate_->global_handles()->IterateYoungStrongAndDependentRoots(
        &root_scavenge_visitor);
    isolate_->traced_handles()->IterateYoungRoots(&root_scavenge_visitor);
    scavengers[kMainThreadId]->Publish();

    // 并行处理对象
    auto job =
        std::make_unique<JobTask>(this, &scavengers, std::move(memory_chunks),
                                  &copied_list, &promotion_list);
    V8::GetCurrentPlatform()
        ->CreateJob(v8::TaskPriority::kUserBlocking, std::move(job))
        ->Join();

    // 处理弱引用
    ProcessWeakReferences(&ephemeron_table_list);
  }

  // 其他清理和更新操作
  SemiSpaceNewSpace* semi_space_new_space =
      SemiSpaceNewSpace::From(heap_->new_space());
  semi_space_new_space->set_age_mark_to_top();
  heap_->new_lo_space()->FreeDeadObjects(
      [](Tagged<HeapObject>) { return true; });

  // 更新全局和追踪句柄
  isolate_->global_handles()->UpdateListOfYoungNodes();
  isolate_->traced_handles()->UpdateListOfYoungNodes();
}

Scavenger 类

Scavenger 类负责实际的垃圾回收操作,包括对象的标记和复制。主要方法包括:

  • Process:处理被标记的对象,进行垃圾回收。
  • Finalize:最终阶段,合并和清理回收结果。
  • ScavengePage:处理内存页中的对象。
void Scavenger::Process(JobDelegate* delegate) {
  ScavengeVisitor scavenge_visitor(this);

  bool done;
  size_t objects = 0;
  do {
    done = true;
    ObjectAndSize object_and_size;
    while (!promotion_list_local_.ShouldEagerlyProcessPromotionList() &&
           copied_list_local_.Pop(&object_and_size)) {
      scavenge_visitor.Visit(object_and_size.first);
      done = false;
      if (delegate && ((++objects % kInterruptThreshold) == 0)) {
        if (!copied_list_local_.IsLocalEmpty()) {
          delegate->NotifyConcurrencyIncrease();
        }
      }
    }

    struct PromotionListEntry entry;
    while (promotion_list_local_.Pop(&entry)) {
      Tagged<HeapObject> target = entry.heap_object;
      IterateAndScavengePromotedObject(target, entry.map, entry.size);
      done = false;
      if (delegate && ((++objects % kInterruptThreshold) == 0)) {
        if (!promotion_list_local_.IsGlobalPoolEmpty()) {
          delegate->NotifyConcurrencyIncrease();
        }
      }
    }
  } while (!done);
}

RootScavengeVisitor 类

RootScavengeVisitor 类负责遍历根对象并标记需要回收的对象。

void RootScavengeVisitor::ScavengePointer(FullObjectSlot p) {
  Tagged<Object> object = *p;
  if (Heap::InYoungGeneration(object)) {
    scavenger_->ScavengeObject(FullHeapObjectSlot(p), Cast<HeapObject>(object));
  }
}

通过这些类和方法的协同工作,V8 引擎能够高效地进行新生代对象的垃圾回收,确保内存的及时释放和重用。整个过程可以总结为:

  1. 准备工作:调用 GarbageCollectionPrologueEvacuatePrologue 方法准备垃圾回收。
  2. 遍历对象:使用 IterateAndScavengePromotedObjectsVisitor 类遍历和处理被提升的对象。
  3. 并行处理:通过多个 Scavenger 实例并行处理对象,提升性能。
  4. 更新引用:更新全局句柄列表中的引用,确保没有悬挂引用。
  5. 完成工作:调用 Finalize 方法完成垃圾回收,并更新新生代空间的状态。

结语

以上只是我简单的一些分析,其实里面还有很多其他的文件,以及需要注意的地方。比如内存分配策略的实现,标记工作的具体实现,以及内存压缩技术,垃圾回收触发的机制等等,都有待分析。分析归分析,在实际开发中,我们应该注意以下几个点:

  1. 避免频繁创建短生命周期的对象
  2. 合理使用内存,及时释放不需要的引用
  3. 使用性能分析工具识别和解决内存问题
  4. 理解垃圾回收的工作原理,编写GC友好的代码

浏览器的源码是真的牛,还是多学学吧,现在学C++应该还来得及,卷不动了。