前言:在 Node.js 中我们有时候会使用 global.gc() 主动触发 gc 来测试一些代码,因为我们知道 V8 gc 的执行时机是不定的。但是可能很少同学知道 global.gc() 的实现,本文介绍一些在 V8 中关于这部分的实现。
了解 global.gc() 实现之前,首先看一下 V8 的 Extension 机制。Extension 机制用于拓展 V8 的能力。在 V8 初始化的过程中,V8::Initialize 会初始化 Extension 机制,具体在 Bootstrapper::InitializeOncePerProcess 中。
void Bootstrapper::InitializeOncePerProcess() {
v8::RegisterExtension(std::make_unique<GCExtension>(GCFunctionName()));
v8::RegisterExtension(std::make_unique<ExternalizeStringExtension>());
v8::RegisterExtension(std::make_unique<StatisticsExtension>());
v8::RegisterExtension(std::make_unique<TriggerFailureExtension>());
v8::RegisterExtension(std::make_unique<IgnitionStatisticsExtension>());
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
V8 通过 RegisterExtension 注册了多个 Extension。
void RegisterExtension(std::unique_ptr<Extension> extension) {
RegisteredExtension::Register(std::move(extension));
}
void RegisteredExtension::Register(std::unique_ptr<Extension> extension) {
RegisteredExtension* new_extension = new RegisteredExtension(std::move(extension));
new_extension->next_ = first_extension_;
first_extension_ = new_extension;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
执行完 Register 后就形成了一个 Extension 链表,RegisteredExtension 对象只是对 Extension 对象的简单封装,它内部持有 Extension 对象和一个链表 next 指针,另外还有一个全局的对象 first_extension_ 指向链表头部。注册完 Extension 后,接下来看看初始化 Extension 的逻辑。具体的调用链是 NewContext -> CreateEnvironment -> InvokeBootstrapper.Invoke -> Bootstrapper::CreateEnvironment -> InstallExtensions -> Genesis::InstallExtensions。
bool Genesis::InstallExtensions(Isolate* isolate,
Handle<Context> native_context,
v8::ExtensionConfiguration* extensions) {
ExtensionStates extension_states;
return InstallAutoExtensions(isolate, &extension_states) &&
(!FLAG_expose_gc || InstallExtension(isolate, "v8/gc", &extension_states))
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
当启动 V8 的时候设置了 expose_gc 标记,那么就会执行 InstallExtension。
bool Genesis::InstallExtension(Isolate* isolate,
v8::RegisteredExtension* current,
ExtensionStates* extension_states) {
HandleScope scope(isolate);
extension_states->set_state(current, VISITED);
v8::Extension* extension = current->extension();
// 安装依赖
for (int i = 0; i < extension->dependency_count(); i++) {
if (!InstallExtension(isolate, extension->dependencies()[i],
extension_states)) {
return false;
}
}
// 编译 Extension
CompileExtension(isolate, extension);
extension_states->set_state(current, INSTALLED);
return true;
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
至此就完成了 Extension 的安装。接下来具体看一下 global.gc() 对应的具体实现。
class V8_EXPORT Extension {
public:
Extension(const char* name, const char* source = nullptr, int dep_count = 0,
const char** deps = nullptr, int source_length = -1);
virtual ~Extension() { delete source_; }
virtual Local<FunctionTemplate> GetNativeFunctionTemplate(
Isolate* isolate, Local<String> name) {
return Local<FunctionTemplate>();
}
const char* name() const { return name_; }
size_t source_length() const { return source_length_; }
const String::ExternalOneByteStringResource* source() const {
return source_;
}
int dependency_count() const { return dep_count_; }
const char** dependencies() const { return deps_; }
void set_auto_enable(bool value) { auto_enable_ = value; }
bool auto_enable() { return auto_enable_; }
// Disallow copying and assigning.
Extension(const Extension&) = delete;
void operator=(const Extension&) = delete;
private:
const char* name_;
size_t source_length_; // expected to initialize before source_
String::ExternalOneByteStringResource* source_;
int dep_count_;
const char** deps_;
bool auto_enable_;
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
Extension 是 Extension 机制的基类,从上面代码中我们可以大致了解到一个 Extension 需要实现什么。接着看 gc Extension 的实现。
class GCExtension : public v8::Extension {
public:
explicit GCExtension(const char* fun_name)
: v8::Extension("v8/gc", BuildSource(buffer_, sizeof(buffer_), fun_name)) {}
v8::Local<v8::FunctionTemplate> GetNativeFunctionTemplate(
v8::Isolate* isolate, v8::Local<v8::String> name) override;
static void GC(const v8::FunctionCallbackInfo<v8::Value>& args);
private:
static const char* BuildSource(char* buf, size_t size, const char* fun_name) {
base::SNPrintF(base::Vector<char>(buf, static_cast<int>(size)),
"native function %s();", fun_name);
return buf;
}
char buffer_[50];
};
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
在 bytecode-generator.cc 中有以下代码。
v8::Local<v8::FunctionTemplate> info = expr->extension()->GetNativeFunctionTemplate(v8_isolate, Utils::ToLocal(expr->name()));
- 1.
所以我们来看一下 GetNativeFunctionTemplate。
首先看 GetNativeFunctionTemplate。
```c
v8::Local<v8::FunctionTemplate> GCExtension::GetNativeFunctionTemplate(
v8::Isolate* isolate, v8::Local<v8::String> str) {
return v8::FunctionTemplate::New(isolate, GCExtension::GC);
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
大致就是当我们执行 global.gc() 时就会执行 GCExtension::GC 函数。
void GCExtension::GC(const v8::FunctionCallbackInfo<v8::Value>& args) {
v8::Isolate* isolate = args.GetIsolate();
// 没有参数则同步执行 kFullGarbageCollection gc,即执行 global.gc() 时
if (args.Length() == 0) {
InvokeGC(isolate, ExecutionType::kSync,
v8::Isolate::GarbageCollectionType::kFullGarbageCollection);
return;
}
auto maybe_options = Parse(isolate, args);
if (maybe_options.IsNothing()) return;
GCOptions options = maybe_options.ToChecked();
// 否则根据参数处理
switch (options.execution) {
case ExecutionType::kSync:
InvokeGC(isolate, ExecutionType::kSync, options.type);
break;
case ExecutionType::kAsync: {
v8::HandleScope scope(isolate);
auto resolver = v8::Promise::Resolver::New(isolate->GetCurrentContext())
.ToLocalChecked();
args.GetReturnValue().Set(resolver->GetPromise());
auto task_runner =
V8::GetCurrentPlatform()->GetForegroundTaskRunner(isolate);
CHECK(task_runner->NonNestableTasksEnabled());
task_runner->PostNonNestableTask(
std::make_unique<AsyncGC>(isolate, resolver, options.type));
} break;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
从这个函数中我们可以知道,global.gc 函数是可以带参数的,参数可以控制 gc 是同步还是异步,还可以控制 gc 的类型,我们知道 V8 里针对不同的 space 有不同的 gc 策略。参数的具体函数使用可以参考。
Provides garbage collection on invoking |fun_name|(options), where
- options is a dictionary like object. See supported properties below.
- no parameter refers to options:
{type: 'major', execution: 'sync'}.
- truthy parameter that is not setting any options:
{type: 'minor', execution: 'sync'}.
Supported options:
- type: 'major' or 'minor' for full GC and Scavenge, respectively.
- execution: 'sync' or 'async' for synchronous and asynchronous execution,
respectively.
- Defaults to {type: 'major', execution: 'sync'}.
Returns a Promise that resolves when GC is done when asynchronous execution
is requested, and undefined otherwise.
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
继续看核心函数 InvokeGC。
void InvokeGC(v8::Isolate* isolate, ExecutionType execution_type,
v8::Isolate::GarbageCollectionType type) {
Heap* heap = reinterpret_cast<Isolate*>(isolate)->heap();
switch (type) {
case v8::Isolate::GarbageCollectionType::kMinorGarbageCollection:
heap->CollectGarbage(i::NEW_SPACE, i::GarbageCollectionReason::kTesting,
kGCCallbackFlagForced);
break;
case v8::Isolate::GarbageCollectionType::kFullGarbageCollection:
EmbedderStackStateScope stack_scope(
heap,
execution_type == ExecutionType::kAsync
? EmbedderStackStateScope::kImplicitThroughTask
: EmbedderStackStateScope::kExplicitInvocation,
execution_type == ExecutionType::kAsync
? v8::EmbedderHeapTracer::EmbedderStackState::kNoHeapPointers
: v8::EmbedderHeapTracer::EmbedderStackState::
kMayContainHeapPointers);
heap->PreciseCollectAllGarbage(i::Heap::kNoGCFlags,
i::GarbageCollectionReason::kTesting,
kGCCallbackFlagForced);
break;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
InvokeGC 就是根据同步的参数去调 heap 对象的 gc 接口从而做不同类型的 gc 回收。这就是 global.gc 的大致实现。
除此之外,还有其他 Extension,我们也可以自己写拓展,不过有点限制的是需要在 V8 初始化时就设置需要安装的 Extension。例如我们可以设置 expose_statistics 标记,然后通过全局函数收集堆信息(不同的 V8 版本支持的不一样)。
node -expose_statistics -e "console.log(global.getV8Statistics())"
- 1.
另外 V8 内部也实现了一些 Extension,包括内置的和一些 Demo,有兴趣的同学可以自行查看。