ScavengerCollector::CollectGarbage()函数内容如下:

  • 交换两个半空间
  • 从Root set迭代可达对象,包括old-to-new引用
  • 迁移可达存活对象
  • 更新指针
showLineNumbers
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
void ScavengerCollector::CollectGarbage() {
ScopedFullHeapCrashKey collect_full_heap_dump_if_crash(isolate_);

DCHECK(surviving_new_large_objects_.empty());
DCHECK(!quarantined_page_sweeper_.IsSweeping());

SemiSpaceNewSpace* new_space = SemiSpaceNewSpace::From(heap_->new_space());
new_space->GarbageCollectionPrologue();
new_space->SwapSemiSpaces();

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

DCHECK(!heap_->allocator()->new_space_allocator()->IsLabValid());

Scavenger::EmptyChunksList empty_chunks;
Scavenger::CopiedList copied_list;
Scavenger::PinnedList pinned_list;
Scavenger::PromotedList promoted_list;
EphemeronRememberedSet::TableList ephemeron_table_list;

PinnedObjects pinned_objects;

const int num_scavenge_tasks = NumberOfScavengeTasks();
std::vector<std::unique_ptr<Scavenger>> scavengers;

const bool is_logging = isolate_->log_object_relocation();
for (int i = 0; i < num_scavenge_tasks; ++i) {
scavengers.emplace_back(
new Scavenger(this, heap_, is_logging, &empty_chunks, &copied_list,
&pinned_list, &promoted_list, &ephemeron_table_list));
}
Scavenger& main_thread_scavenger = *scavengers[kMainThreadId].get();

{
// Identify weak unmodified handles. Requires an unmodified graph.
TRACE_GC(heap_->tracer(),
GCTracer::Scope::SCAVENGER_SCAVENGE_WEAK_GLOBAL_HANDLES_IDENTIFY);
isolate_->traced_handles()->ComputeWeaknessForYoungObjects();
}

std::vector<std::pair<ParallelWorkItem, MutablePageMetadata*>>
old_to_new_chunks;
{
// Copy roots.
TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_ROOTS);

// We must collect old-to-new pages before starting Scavenge because
// pages could be removed from the old generation for allocation which
// hides them from the iteration.
OldGenerationMemoryChunkIterator::ForAll(
heap_, [&old_to_new_chunks](MutablePageMetadata* chunk) {
if (chunk->slot_set<OLD_TO_NEW>() ||
chunk->typed_slot_set<OLD_TO_NEW>() ||
chunk->slot_set<OLD_TO_NEW_BACKGROUND>()) {
old_to_new_chunks.emplace_back(ParallelWorkItem{}, chunk);
}
});

const Heap::StackScanMode stack_scan_mode =
heap_->ConservativeStackScanningModeForMinorGC();
DCHECK_IMPLIES(stack_scan_mode == Heap::StackScanMode::kSelective,
heap_->IsGCWithStack());
if ((stack_scan_mode != Heap::StackScanMode::kNone) &&
heap_->IsGCWithStack()) {
// Pinning objects must be the first step and must happen before
// scavenging any objects. Specifically we must all pin all objects
// before visiting other pinned objects. If we scavenge some object X
// and move it before all stack-reachable objects are pinned, and we
// later find that we need to pin X, it will be too late to undo the
// moving.
TRACE_GC(heap_->tracer(),
GCTracer::Scope::SCAVENGER_SCAVENGE_PIN_OBJECTS);
ConservativeObjectPinningVisitor conservative_pinning_visitor(
heap_, main_thread_scavenger, pinned_objects);
// Scavenger reuses the page's marking bitmap as a temporary object
// start bitmap. Stack scanning will incrementally build the map as it
// searches through pages.
YoungGenerationConservativeStackVisitor stack_visitor(
isolate_, &conservative_pinning_visitor);
// Marker was already set by Heap::CollectGarbage.
heap_->IterateConservativeStackRoots(&stack_visitor, stack_scan_mode);
if (V8_UNLIKELY(v8_flags.stress_scavenger_conservative_object_pinning)) {
TreatConservativelyVisitor handles_visitor(&stack_visitor, heap_);
heap_->IterateRootsForPrecisePinning(&handles_visitor);
}
}
const bool is_using_precise_pinning =
heap_->ShouldUsePrecisePinningForMinorGC();
if (is_using_precise_pinning) {
PreciseObjectPinningVisitor precise_pinning_visitor(
heap_, main_thread_scavenger, pinned_objects);
ClearStaleLeftTrimmedPointerVisitor left_trim_visitor(
heap_, &precise_pinning_visitor);
heap_->IterateRootsForPrecisePinning(&left_trim_visitor);
}

// Scavenger treats all weak roots except for global handles as strong.
// That is why we don't set skip_weak = true here and instead visit
// global handles separately.
base::EnumSet<SkipRoot> options(
{SkipRoot::kExternalStringTable, SkipRoot::kGlobalHandles,
SkipRoot::kTracedHandles, SkipRoot::kOldGeneration,
SkipRoot::kConservativeStack, SkipRoot::kReadOnlyBuiltins});
if (is_using_precise_pinning) {
options.Add({SkipRoot::kMainThreadHandles, SkipRoot::kStack});
}
RootScavengeVisitor root_scavenge_visitor(main_thread_scavenger);

heap_->IterateRoots(&root_scavenge_visitor, options);
isolate_->global_handles()->IterateYoungStrongAndDependentRoots(
&root_scavenge_visitor);
isolate_->traced_handles()->IterateYoungRoots(&root_scavenge_visitor);
}
{
// Parallel phase scavenging all copied and promoted objects.
TRACE_GC_ARG1(heap_->tracer(),
GCTracer::Scope::SCAVENGER_SCAVENGE_PARALLEL_PHASE,
"UseBackgroundThreads", heap_->ShouldUseBackgroundThreads());

auto job = std::make_unique<JobTask>(
this, &scavengers, std::move(old_to_new_chunks), copied_list,
pinned_list, promoted_list);
TRACE_GC_NOTE_WITH_FLOW("Parallel scavenge started", job->trace_id(),
TRACE_EVENT_FLAG_FLOW_OUT);
V8::GetCurrentPlatform()
->CreateJob(v8::TaskPriority::kUserBlocking, std::move(job))
->Join();
DCHECK(copied_list.IsEmpty());
DCHECK(pinned_list.IsEmpty());
DCHECK(promoted_list.IsEmpty());
}

{
// Scavenge weak global handles.
TRACE_GC(heap_->tracer(),
GCTracer::Scope::SCAVENGER_SCAVENGE_WEAK_GLOBAL_HANDLES_PROCESS);
GlobalHandlesWeakRootsUpdatingVisitor visitor;
isolate_->global_handles()->ProcessWeakYoungObjects(
&visitor, &IsUnscavengedHeapObjectSlot);
isolate_->traced_handles()->ProcessWeakYoungObjects(
&visitor, &IsUnscavengedHeapObjectSlot);
}

{
// Finalize parallel scavenging.
TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_FINALIZE);

DCHECK(surviving_new_large_objects_.empty());

for (auto& scavenger : scavengers) {
scavenger->Finalize();
}
scavengers.clear();

#ifdef V8_COMPRESS_POINTERS
// Sweep the external pointer table.
DCHECK(heap_->concurrent_marking()->IsStopped());
DCHECK(!heap_->incremental_marking()->IsMajorMarking());
heap_->isolate()->external_pointer_table().Sweep(
heap_->young_external_pointer_space(), heap_->isolate()->counters());
#endif // V8_COMPRESS_POINTERS

HandleSurvivingNewLargeObjects();

heap_->tracer()->SampleConcurrencyEsimate(
FetchAndResetConcurrencyEstimate());
}

{
// Update references into new space
TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_SCAVENGE_UPDATE_REFS);
heap_->UpdateYoungReferencesInExternalStringTable(
&Heap::UpdateYoungReferenceInExternalStringTableEntry);

if (V8_UNLIKELY(v8_flags.always_use_string_forwarding_table)) {
isolate_->string_forwarding_table()->UpdateAfterYoungEvacuation();
}
}

ProcessWeakReferences(&ephemeron_table_list);

{
TRACE_GC(heap_->tracer(),
GCTracer::Scope::SCAVENGER_SCAVENGE_RESTORE_AND_QUARANTINE_PINNED);
RestorePinnedObjects(*new_space, pinned_objects);
QuarantinePinnedPages(*new_space);
}

// Need to free new space LAB that was allocated during scavenge.
heap_->allocator()->new_space_allocator()->FreeLinearAllocationArea();

// Since we promote all surviving large objects immediately, all remaining
// large objects must be dead.
// TODO(hpayer): Don't free all as soon as we have an intermediate generation.
heap_->new_lo_space()->FreeDeadObjects(
[](Tagged<HeapObject>) { return true; });

new_space->GarbageCollectionEpilogue();

// Start sweeping quarantined pages.
if (!pinned_objects.empty()) {
quarantined_page_sweeper_.StartSweeping(std::move(pinned_objects));
} else {
// Sweeping is not started since there are no pages to sweep. Mark sweeping
// as completed so that the current GC cycle can be stopped since there is
// no sweeper to mark it has completed later.
heap_->tracer()->NotifyYoungSweepingCompleted();
}

{
TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_FREE_REMEMBERED_SET);
Scavenger::EmptyChunksList::Local empty_chunks_local(empty_chunks);
MutablePageMetadata* chunk;
while (empty_chunks_local.Pop(&chunk)) {
RememberedSet<OLD_TO_NEW>::CheckPossiblyEmptyBuckets(chunk);
RememberedSet<OLD_TO_NEW_BACKGROUND>::CheckPossiblyEmptyBuckets(chunk);
}

#ifdef DEBUG
OldGenerationMemoryChunkIterator::ForAll(
heap_, [](MutablePageMetadata* chunk) {
if (chunk->slot_set<OLD_TO_NEW>() ||
chunk->typed_slot_set<OLD_TO_NEW>() ||
chunk->slot_set<OLD_TO_NEW_BACKGROUND>()) {
DCHECK(chunk->possibly_empty_buckets()->IsEmpty());
}
});
#endif
}

SweepArrayBufferExtensions();

isolate_->global_handles()->UpdateListOfYoungNodes();
isolate_->traced_handles()->UpdateListOfYoungNodes();

// Update how much has survived scavenge.
heap_->IncrementYoungSurvivorsCounter(heap_->SurvivedYoungObjectSize());

{
TRACE_GC(heap_->tracer(), GCTracer::Scope::SCAVENGER_RESIZE_NEW_SPACE);
heap_->ResizeNewSpace();
}
}

贴一下copilot的回复

Scavenger垃圾回收的完整流程

第1阶段:准备和初始化

1
2
3
4
5
6
7
8
// 1. 空间翻转:From <-> To 空间角色交换
new_space->SwapSemiSpaces();
heap_->new_lo_space()->Flip();

// 2. 初始化工作列表和Scavenger实例
Scavenger::CopiedList copied_list; // 已复制到To空间的对象
Scavenger::PromotedList promoted_list; // 提升到老年代的对象
Scavenger::PinnedList pinned_list; // 固定在原位置的对象

第2阶段:对象固定(在栈扫描模式下)

1
2
3
4
// 保守栈扫描,固定可能被栈引用的对象
ConservativeObjectPinningVisitor conservative_pinning_visitor(...);
YoungGenerationConservativeStackVisitor stack_visitor(...);
heap_->IterateConservativeStackRoots(&stack_visitor, stack_scan_mode);

固定策略

  • 扫描栈上所有可能的指针值
  • 将指向From空间的对象标记为固定
  • 固定对象不会被移动,保持原地不动

第3阶段:根对象扫描

1
2
3
4
5
6
7
8
9
10
11
// 1. 收集OLD_TO_NEW记忆集页面
OldGenerationMemoryChunkIterator::ForAll(heap_, [&](MutablePageMetadata* chunk) {
if (chunk->slot_set<OLD_TO_NEW>()) {
old_to_new_chunks.emplace_back(ParallelWorkItem{}, chunk);
}
});

// 2. 扫描各种根对象
RootScavengeVisitor root_scavenge_visitor(main_thread_scavenger);
heap_->IterateRoots(&root_scavenge_visitor, options);
isolate_->global_handles()->IterateYoungStrongAndDependentRoots(&root_scavenge_visitor);

第4阶段:并行复制和处理

1
2
3
4
5
6
7
8
9
10
11
12
// 并行任务处理
void ScavengerCollector::JobTask::ProcessItems(JobDelegate* delegate,
Scavenger* scavenger) {
// 1. 访问固定对象
scavenger->VisitPinnedObjects();

// 2. 并发扫描OLD_TO_NEW页面
ConcurrentScavengePages(scavenger);

// 3. 处理复制和提升对象
scavenger->Process(delegate);
}

4.1 页面扫描处理

1
2
3
4
5
6
7
8
9
10
11
12
void Scavenger::ScavengePage(MutablePageMetadata* page) {
// 处理OLD_TO_NEW记忆集
RememberedSet<OLD_TO_NEW>::IterateAndTrackEmptyBuckets(
page, [this](MaybeObjectSlot slot) {
return CheckAndScavengeObject(heap_, slot);
}, &local_empty_chunks_);

// 处理可执行页面的类型化槽位
if (chunk->executable()) {
RememberedSet<OLD_TO_NEW>::IterateTyped(page, ...);
}
}

4.2 对象处理循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void Scavenger::Process(JobDelegate* delegate) {
ScavengeVisitor scavenge_visitor(this);

do {
// 处理已复制对象列表
while (local_copied_list_.Pop(&object)) {
scavenge_visitor.Visit(object); // 扫描对象引用
}

// 处理已提升对象列表
while (local_promoted_list_.Pop(&entry)) {
IterateAndScavengePromotedObject(entry.heap_object, entry.map, entry.size);
}
} while (!done);
}

第5阶段:对象移动策略

复制算法的三种结果

  1. 复制到To空间

    • 年轻的小对象
    • 在To空间分配新位置
    • 在原对象位置留下转发指针
  2. 提升到老年代

    • 存活过一次GC的对象
    • 大小超过提升阈值的对象
    • 直接分配到老年代空间
  3. 固定在原位置

    • 被栈指针可能引用的对象
    • 使用自转发指针标记
    • 所在页面被隔离

第6阶段:弱引用处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 清理新生代中的弱引用表
void ScavengerCollector::ClearYoungEphemerons(
EphemeronRememberedSet::TableList* ephemeron_table_list) {
ephemeron_table_list->Iterate([this](Tagged<EphemeronHashTable> table) {
for (InternalIndex i : table->IterateEntries()) {
Tagged<HeapObject> key = key_slot.ToHeapObject();
if (IsUnscavengedHeapObject(heap_, key)) {
table->RemoveEntry(i); // 键对象已死,删除条目
} else {
Tagged<HeapObject> forwarded = ForwardingAddress(key);
key_slot.StoreHeapObject(forwarded); // 更新转发地址
}
}
});
}

第7阶段:清理和整理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
// 恢复固定对象的原始状态
RestorePinnedObjects(*new_space, pinned_objects);
QuarantinePinnedPages(*new_space);
}

// 清理记忆集
{
Scavenger::EmptyChunksList::Local empty_chunks_local(empty_chunks);
MutablePageMetadata* chunk;
while (empty_chunks_local.Pop(&chunk)) {
RememberedSet<OLD_TO_NEW>::CheckPossiblyEmptyBuckets(chunk);
RememberedSet<OLD_TO_NEW_BACKGROUND>::CheckPossiblyEmptyBuckets(chunk);
}
}

// 调整新生代大小
heap_->ResizeNewSpace();

Scavenger的关键特性

1. 并行化

  • 多个Scavenger实例并行工作
  • 使用工作窃取算法分配任务
  • 线程安全的工作列表管理

2. 增量处理

  • 对象按批次处理,避免长时间停顿
  • 支持任务抢占和恢复

3. 保守栈扫描

  • 处理栈上可能的指针引用
  • 使用对象固定避免移动被引用对象

4. 记忆集优化

  • 高效处理老年代到新生代的引用
  • 减少全堆扫描的需要

5. 自适应策略

  • 根据对象存活时间决定复制或提升
  • 动态调整新生代大小

这种设计使得Scavenger能够高效地回收新生代垃圾,同时最小化停顿时间,是V8垃圾回收系统的重要组成部分。