前言:GC 是一个古老、复杂并且很 Cool 的技术,本文大概介绍一下早期 V8 中关于 GC 实现的部分,代码版本 0.1.5,早期版本利于快速理解整体的逻辑,因为现代版本已经非常复杂。
HandleScope 和 Handle
首先看一下 Handle 一般的用法,Handle 是 GC 非常核心的概念。
HandleScope scope;
Handle<JSObject> obj(object);
这两句代码里涉及了两个核心的概念。首先看看 HandleScope。
class HandleScope {
public:
// 记录之前的上下文
HandleScope() : previous_(current_){
current_.extensions = 0;
}
~HandleScope() {
// 如果自己申请了内存,则释放
if (current_.extensions > 0) DeleteExtensions();
// 出栈,指向前面那个
current_ = previous_;
}
static void** CreateHandle(void* value);
class Data {
public:
// 分配的内存块数
int extensions;
// 下一个可用的位置
void** next;
// 达到limit执行的地址后说明当前内存块用完了
void** limit;
inline void Initialize() {
extensions = -1;
next = limit = NULL;
}
};
// 当前的HandleScope
static Data current_;
// 上一个HandleScope
const Data previous_;
}
首先看一下 Data 数据结构,Data 有三个字段 :
- next 表示下次创建 Handle 时使用的地址。
- limit表示超过 limit 则需要重新申请内存,V8 是按块申请的,limit 就是块的末地址。
- extensions 表示该 HandleScope 申请了多少块内存。
接着继续看 current 类静态变量,这个变量记录了当前用于分配 Handle 的地址信息,接着看 HandleScope 类本身。HandleScope 是基于 RAII 机制管理一个函数中,多个 Handle 创建和销毁的对象。HandleScope 在构造函数中记录了当前的上下文,然后在析构函数中进行恢复。我们看到在 HandleScope 的构造函数中把当前上下文记录在 previous_ 里,然后重置 extensions,表示该 HandleScope 还没有申请内存,因为新的 HandleScope 会沿用前一个 HandleScope 的空闲内存,因为 V8 只把 current 赋值给 previous_ ,然后接着使用 current 去分配内存,只有当 HandleScope 内存不够时,才会分配新的内存块。看一下下面的图。
首先看一下上图的左边,当我们创建第一个 HandleScope 时,该 HandleScope 对象就会保存当前 HandleScope 的上下文,这时候当前 HandleScope 上下文是 NULL,然后创建第一个 Handle 时因为 next == limit == NULL,所以需要申请一块内存,即 extensions 为 1。当创建第二个 Handle 后,就变成了左上角的样子。接着我们又定义了第二个 HandleScope,那么首先 第二个 HandleScope 同样在 previous_ 中记录当前的上下文,即 current 的值,接着在第二个 HandleScope 中分配一个 Handle 就变成了右图的样子。接下来看退出 HandleScope 时的逻辑,从代码中可以看到,首先判断本 HandleScope 是否创建了额外的内存块,是则释放,然后把 previous_ 中保存的上下文赋值给 current,那么 current 就恢复到了上一个 HandleScope 的上下文。
了解了 HandleScope 和 Handle 之后,我们接下来分析一下堆内存,主要是分析内存分配器、新生代和老生代(不包括代码区,map 区等)。
内存分配器 MemoryAllocator
V8 堆内存的申请和回收由内存分配器管理,新生代和老生代只是负责使用。首先看看 MemoryAllocator 的定义。
class MemoryAllocator : public AllStatic {
public:
static const int kMaxNofChunks = 1 << Page::kPageSizeBits;
static const int kPagesPerChunk = 64;
static const int kChunkSize = kPagesPerChunk * Page::kPageSize;
private:
// Maximum space size in bytes.
static int capacity_;
// Allocated space size in bytes.
static int size_;
// The initial chunk of virtual memory.
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_;
};
MemoryAllocator 中由 chunks_ 负责管理具体的内存,chunks_ 是一个 ChunkInfo 链表,从 ChunkInfo 中可以看到它主要记录了内存的起始地址和大小,以及所属的 space(新生代、老生代)。
接下来看 MemoryAllocator 的初始化。
// Setup memory allocator and allocate an initial chunk of memory. The
// initialchunk is double the size of the new space to ensure that we can
// find a pair of semispaces that are contiguous and aligned to their size.
// capacity 最大的内存大小(新生代 + 老生代)
bool MemoryAllocator::Setup(int capacity) {
// 页的整数倍
capacity_ = RoundUp(capacity, Page::kPageSize);
// 最大的chunk数,
max_nof_chunks_ = (capacity_ / (kChunkSize - Page::kPageSize)) + 4;
size_ = 0;
ChunkInfo info;
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* chunk = MemoryAllocator::ReserveInitialChunk(2 * young_generation_size_);
接着看 ReserveInitialChunk。
void* MemoryAllocator::ReserveInitialChunk(const size_t requested) {
// 新建一个VM对象,分配size的虚拟内存,记录在VM对象
initial_chunk_ = new VirtualMemory(requested);
size_ += requested;
// 返回分配内存的首地址
return initial_chunk_->address();}VirtualMemory::VirtualMemory(size_t size, void* address_hint) {
// 映射一块内存
address_ = mmap(address_hint, size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE,
kMmapFd, kMmapFdOffset);
size_ = size;
}
这就完成了内存的申请。
新生代堆内存管理
// 刚才分配的空间中,老生代在开始位置
Address old_space_start = reinterpret_cast<Address>(chunk);
// 紧接着是新生代,算出大小是young_generation_size_的n倍,值大于old_space_start的最小值
Address new_space_start = RoundUp(old_space_start, young_generation_size_);
// 代码空间等于新生代开始+新生代大小Address code_space_start = new_space_start + young_generation_size_;
// 老生代空间大小int old_space_size = new_space_start - old_space_start;
/*
因为chunk的空间两倍的young_generation_size_,新生代大小占了一半,
所以还有一半,剩下的一半老生代占了old_space_size,所以剩下的代码区大小
*/
因为chunk的空间两倍的young_generation_size_,新生代大小占了一半,
所以还有一半,剩下的一半老生代占了old_space_size,所以剩下的代码区大小
*/int code_space_size = young_generation_size_ - old_space_size;/*
|young_generation_size_|
chunk => -----------------------------------
^ ^ ^ ^
| | | |
old new code end*/
// 分配一个管理新生代地址空间的对象,传入初始值和最大值
// 因为新生代分配from和to,所以这两个初始化值是每个空间的属性
new_space_ = new NewSpace(initial_semispace_size_, semispace_size_);
进行了一系列的计算后,开始初始化新生代。接下来看 NewSpace。
class NewSpace : public Malloced {
public:
// Create a new space with a given allocation capacity (ie, the capacity of
// *one* of the semispaces). The constructor does not allocate heap memory
// from the OS. When the space is set up, it is given a contiguous chunk of
// memory of size 2 * semispace_capacity. To support fast containment
// testing in the new space, the size of this chunk must be a power of two
// and it must be aligned to its size.
NewSpace(int initial_semispace_capacity, int maximum_semispace_capacity);
private:
int capacity_;
int maximum_capacity_;
// The semispaces.
SemiSpace* to_space_;
SemiSpace* from_space_;
Address start_;
uint32_t address_mask_;
uint32_t object_mask_;
uint32_t object_expected_;
/*
struct AllocationInfo {
Address top; // current allocation top
Address limit; // current allocation limit
};
*/
// 管理新生代内存的数据结构
AllocationInfo allocation_info_;
AllocationInfo mc_forwarding_info_;
};
新生代分为两个 SemiSpace。结构差不多,就不再介绍。了解了定义,看看初始化的逻辑。
// 设置需要管理的地址空间,start是首地址,size是大小bool NewSpace::Setup(Address start, int size) {
// to区
if (to_space_ == NULL
|| !to_space_->Setup(start, maximum_capacity_)) {
return false;
}
// from区,和to区一人一半
if (from_space_ == NULL
|| !from_space_->Setup(start + maximum_capacity_, maximum_capacity_)) {
return false;
}
// 开始地址
start_ = start;
/*
address_mask的高位是地址的有效位,
size是只有一位为一,减一后一变成0,一右边
的全部0位变成1,然后取反,高位的0变成1,再加上size中本来的1,
即从左往右的1位地址有效位
*/
address_mask_ = ~(size - 1);
// 计算堆对象地址掩码,低位是标记位,判断的时候需要保留,kHeapObjectTag是堆对象的标记
object_mask_ = address_mask_ | kHeapObjectTag;
// 见 contains 函数,对象地址里低位是标记位,判断的时候需要带上
object_expected_ = reinterpret_cast<uint32_t>(start) | kHeapObjectTag;
// 初始化管理的内存信息
allocation_info_.top = to_space_->low();
allocation_info_.limit = to_space_->high();
mc_forwarding_info_.top = NULL;
mc_forwarding_info_.limit = NULL;
return true;
}
至此,新生代的数据结构和初始化分析完成。接下来看老生代,老生代的数据结构和新生代差不多,不再分析,区别是老生代的内存是用链表管理的,而不是连续的内存,直接看初始化流程。
bool PagedSpace::Setup(Address start, size_t size) {
int num_pages = 0;
first_page_ = MemoryAllocator::CommitPages(start, size, this, &num_pages);
// 初始化page链表,清除记录集
for (Page* p = first_page_; p->is_valid(); p = p->next_page()) {
p->ClearRSet();
}
// 设置管理内存分配的结构体,为第一个空闲页
SetAllocationInfo(&allocation_info_, first_page_);
return true;
}
接着看 CommitPages。
// 保存和管理虚拟地址start到start+size的空间
Page* MemoryAllocator::CommitPages(Address start, size_t size, PagedSpace* owner, int* num_pages) {
// chunk中的页数,一个chunk包括多个页
*num_pages = PagesInChunk(start, size);
// 保存内存信息到chunkInfo
int chunk_id = Pop();
chunks_[chunk_id].init(start, size, owner);
// 按页初始化内存信息
return InitializePagesInChunk(chunk_id, *num_pages, owner);
}
继续看 InitializePagesInChunk。
// 初始化某块内存为page管理的结构
Page* MemoryAllocator::InitializePagesInChunk(int chunk_id, int pages_in_chunk, PagedSpace* owner) {
Address chunk_start = chunks_[chunk_id].address();
// 算出有效的开始地址,即要对齐
Address low = RoundUp(chunk_start, Page::kPageSize);
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;
}
// page_addr此时执行最后一页的末尾,减去一页大小得到最后一页的起始地址
Page* last_page = Page::FromAddress(page_addr - Page::kPageSize);
// 下一页地址为0
last_page->opaque_header = OffsetFrom(0) | chunk_id;
return Page::FromAddress(low);
}
一个内存块里面包括多个 Page,上面的代码是按 Page 为单位初始化内存。整体流程完成后,结构图如下。
新建对象。
分析完数据结构和初始化流程,接下来看创建对象时的内存分配。
Local<v8::Object> v8::Object::New() {
i::Handle<i::JSObject> obj = i::Factory::NewJSObject(i::Top::object_function());
return Utils::ToLocal(obj);
}
Handle<JSObject> Factory::NewJSObject(Handle<JSFunction> constructor, PretenureFlag pretenure) {
// CALL_HEAP_FUNCTION 会在分配失败时进行 GC
CALL_HEAP_FUNCTION(Heap::AllocateJSObject(*constructor, pretenure), JSObject);
}
Object* Heap::AllocateJSObject(JSFunction* constructor, PretenureFlag pretenure) {
return AllocateJSObjectFromMap(constructor->initial_map(), pretenure);
}
Object* Heap::AllocateJSObjectFromMap(Map* map, PretenureFlag pretenure) {
Object* properties = AllocatePropertyStorageForMap(map);
// Allocate the JSObject.
// 选择分配的 Space
AllocationSpace space = (pretenure == TENURED) ? OLD_SPACE : NEW_SPACE;
// 对象所需内存太大(map 中记录了大小),则直接在大对象 Space 分配
if (map->instance_size() > MaxHeapObjectSize())
space = LO_SPACE;
// 分配内存
Object* obj = Allocate(map, space);
// Initialize the JSObject.
InitializeJSObjectFromMap(JSObject::cast(obj),
FixedArray::cast(properties),
map);
return obj;
}
接着看 Allocate。
// 根据map的type分配一个对象的空间,然后设置该对象的map属性是map
Object* Heap::Allocate(Map* map, AllocationSpace space) {
Object* result = AllocateRaw(map->instance_size(), space);
// 转成HeapObject才有set_map函数
HeapObject::cast(result)->set_map(map);
return result;
}
// 在哪块空间分配多少内存
Object* Heap::AllocateRaw(int size_in_bytes, AllocationSpace space) {
// 在新生代中分配内存
if (NEW_SPACE == space) {
return new_space_->AllocateRaw(size_in_bytes);
}
Object* result;
// 在对应的空间分配大小
if (OLD_SPACE == space) {
result = old_space_->AllocateRaw(size_in_bytes);
} else if (CODE_SPACE == space) {
result = code_space_->AllocateRaw(size_in_bytes);
} else if (LO_SPACE == space) {
result = lo_space_->AllocateRaw(size_in_bytes);
} else {
result = map_space_->AllocateRaw(size_in_bytes);
}
return result;
}
Object* AllocateRaw(int size_in_bytes) {
// allocation_info_ 就是初始化时记录的内存信息,allocation_info_ 负责管理内存的分配
return AllocateRawInternal(size_in_bytes, &allocation_info_);
}
Object* NewSpace::AllocateRawInternal(int size_in_bytes, AllocationInfo* alloc_info) {
// alloc_info->top保存了该空间当前可分配的地址,加上当前申请分配的
Address new_top = alloc_info->top + size_in_bytes;
// 超过了内存大小
if (new_top > alloc_info->limit) {
return Failure::RetryAfterGC(size_in_bytes, NEW_SPACE);
}
// 地址+低一位的标记,转成堆对象的地址表示,即低位置1
Object* obj = HeapObject::FromAddress(alloc_info->top);
// 更新指针,指向下一块可分配的内存
alloc_info->top = new_top;
return obj;
}
我们可以看到分配内存是非常快的,只需要移动一下指针。
垃圾回收
但是有一个需要注意的是,当内存不够时就需要进行 GC,具体处理逻辑在之前代码的 CALL_HEAP_FUNCTION 宏中。
#define CALL_HEAP_FUNCTION(FUNCTION_CALL, TYPE) \
do { \
GC_GREEDY_CHECK(); \
Object* __object__ = FUNCTION_CALL; \
if (__object__->IsFailure()) { \
if (__object__->IsRetryAfterGC()) { \
if (!Heap::CollectGarbage( \
Failure::cast(__object__)->requested(), \
Failure::cast(__object__)->allocation_space())) { \ /* TODO(1181417): Fix this. */ \
v8::internal::V8::FatalProcessOutOfMemory("CALL_HEAP_FUNCTION"); \ } \
__object__ = FUNCTION_CALL; \ if (__object__->IsFailure()) { \ if (__object__->IsRetryAfterGC()) { \ /* TODO(1181417): Fix this. */ \
v8::internal::V8::FatalProcessOutOfMemory("CALL_HEAP_FUNCTION"); \ } \ return Handle<TYPE>(); \ } \ } else { \ return Handle<TYPE>(); \ } \ } \ return Handle<TYPE>(TYPE::cast(__object__)); \ } while (false)
重点看 Heap::CollectGarbage。
bool Heap::CollectGarbage(int requested_size, AllocationSpace space) {
// The VM is in the GC state until exiting this function.
VMState state(GC);
{ GCTracer tracer;
GarbageCollectionPrologue();
// 选择GC算法
GarbageCollector collector = SelectGarbageCollector(space);
tracer.set_collector(collector);
StatsRate* rate = (collector == SCAVENGER)
? &Counters::gc_scavenger : &Counters::gc_compactor;
rate->Start();
// 开始垃圾回收
PerformGarbageCollection(space, collector);
rate->Stop();
GarbageCollectionEpilogue();
}
// GC 后内存是否能满足当前需要分配的内存
switch (space) {
case NEW_SPACE:
return new_space_->Available() >= requested_size;
case OLD_SPACE:
return old_space_->Available() >= requested_size;
case CODE_SPACE:
return code_space_->Available() >= requested_size;
case MAP_SPACE:
return map_space_->Available() >= requested_size;
case LO_SPACE:
return lo_space_->Available() >= requested_size;
}
return false;
}
早期的 V8 GC 是同步的,还没有并行、并发等优化逻辑,继续 PerformGarbageCollection。在 V8 初始化时会初始化新生代堆内存的数据结构。
void Heap::PerformGarbageCollection(AllocationSpace space, GarbageCollector collector) {
if (collector == MARK_COMPACTOR) {
MarkCompact();
int promoted_space_size = PromotedSpaceSize();
promoted_space_limit_ =
promoted_space_size + Max(2 * MB, (promoted_space_size/100) * 35);
old_gen_exhausted_ = false;
if (space == NEW_SPACE && !MarkCompactCollector::HasCompacted()) {
Scavenge();
}
} else {
Scavenge();
}
}
这里我们可以看到经常听到的 Scavenge 和 MarkCompact 算法。首先看 Scavenge。
void Heap::Scavenge() {
gc_state_ = SCAVENGE;
// from区和to区互换,to区变成空白,from区则分配了各种对象
new_space_->Flip();
// 重置to区的指针,因为to区变空闲了
new_space_->ResetAllocationInfo();
CopyVisitor copy_visitor;
// 遍历根,复制对象到另一个区
IterateRoots(©_visitor);
// 设置临界线,以此判断新生代对象是否可以经历了二次 GC,即可以晋升带老生代
new_space_->set_age_mark(new_mark);
gc_state_ = NOT_IN_GC;
}
实际的逻辑比这个负责很多,不过这里只是大致了解流程。CopyVisitor 对象负责把对象复制到另一个区。
class CopyVisitor: public ObjectVisitor {
public:
void VisitPointer(Object** p) {
CopyObject(p);
}
void VisitPointers(Object** start, Object** end) {
// Copy all HeapObject pointers in [start, end)
for (Object** p = start; p < end; p++) CopyObject(p);
}
private:
void CopyObject(Object** p) {
if (!Heap::InFromSpace(*p)) return;
Heap::CopyObject(reinterpret_cast<HeapObject**>(p));
}
};
继续看 Heap::CopyObject。
void Heap::CopyObject(HeapObject** p) {
ASSERT(InFromSpace(*p));
HeapObject* object = *p;
// map 对象记录了对象的元信息
HeapObject* first_word = object->map();
int object_size = object->SizeFromMap(Map::cast(first_word));
Object* result;
// 是否满足晋升条件
/*
bool Heap::ShouldBePromoted(Address old_address, int object_size) {
// 小于age_mark说明已经经过了一次gc回收了,第二次的时候还存活则可以晋升到老生代
// age_mark是from区的值,但是是在from区和to区交换之后,所以其实是to区的值
return old_address < new_space_->age_mark()
|| (new_space_->Size() + object_size) >= (new_space_->Capacity() >> 2);
}
*/
if (ShouldBePromoted(object->address(), object_size)) {
bool has_pointers =
type != HEAP_NUMBER_TYPE &&
(type >= FIRST_NONSTRING_TYPE ||
String::cast(object)->representation_tag() != kSeqStringTag);
// 在对应的 Space 分配内存,把对象复制过去
if (has_pointers) {
result = old_space_->AllocateRaw(object_size);
} else {
result = code_space_->AllocateRaw(object_size);
}
if (!result->IsFailure()) {
*p = MigrateObject(p, HeapObject::cast(result), object_size);
if (has_pointers) {
promoted_top -= kPointerSize;
Memory::Object_at(promoted_top) = *p;
}
return;
}
}
// 不晋升的复制到新生代的另一个区,下次还存活再晋升
result = new_space_->AllocateRaw(object_size);
*p = MigrateObject(p, HeapObject::cast(result), object_size);
}
MigrateObject 实现了对象的复制。
HeapObject* Heap::MigrateObject(HeapObject** source_p,
HeapObject* target,
int size) {
void** src = reinterpret_cast<void**>((*source_p)->address());
void** dst = reinterpret_cast<void**>(target->address());
int counter = size/kPointerSize - 1;
do {
*dst++ = *src++;
} while (counter-- > 0);
(*source_p)->set_map(reinterpret_cast<Map*>(target));
return target;
}
至此就完成了新生代 GC 的分析,紧接着分析老生代的。
void Heap::MarkCompact() {
gc_state_ = MARK_COMPACT;
MarkCompactPrologue();
// 开始回收
MarkCompactCollector::CollectGarbage();
MarkCompactEpilogue();
gc_state_ = NOT_IN_GC;
}
老生代 GC 比较复杂。
void MarkCompactCollector::CollectGarbage() {
Prepare();
// 标记
MarkLiveObjects();
// 清除大对象
SweepLargeObjectSpace();
// 是否压缩,压缩是为了防止内存碎片,否则直接清楚
if (compacting_collection_) {
EncodeForwardingAddresses();
UpdatePointers();
RelocateObjects();
RebuildRSets();
} else {
SweepSpaces();
}
Finish();
}
这里我们只分析标记和清除,V8 早期也还没有并行标记,延迟清除等逻辑。
void MarkCompactCollector::MarkLiveObjects() {
// 新生代有一半空间时空闲的,可以使用,marking_stack用于遍历扫描对象的数据结构
marking_stack.Initialize(Heap::new_space()->FromSpaceLow(),
Heap::new_space()->FromSpaceHigh());
MarkingVisitor marking_visitor;
// 从根开始遍历,进行标记
Heap::IterateStrongRoots(&marking_visitor);
while (true) {
// IterateStrongRoots 会不断压对象进栈,这里继续遍历,直到为空
MarkObjectsReachableFromTopFrame();
// 遍历的时候,栈可能溢出,内存不够,ok 的话继续遍历其他的
if (!marking_stack.overflowed()) {
if (has_processed_weak_pointers) break;
GlobalHandles::MarkWeakRoots(&MustBeMarked);
GlobalHandles::IterateWeakRoots(&marking_visitor);
has_processed_weak_pointers = true;
continue;
}
// 清除标记
marking_stack.clear_overflowed();
// 继续遍历,到栈为空
ScanOverflowedObjects(&lo_it);
}
}
首先看如何从根开始遍历。
void Heap::IterateStrongRoots(ObjectVisitor* v) {
// 遍历 Handle,忽略其他根
HandleScopeImplementer::Iterate(v);
}
void HandleScopeImplementer::Iterate(ObjectVisitor* v) {
ImplementationUtilities::HandleScopeData* current =
ImplementationUtilities::CurrentHandleScope();
Iterate(v, thread_local.Blocks(), current);
}
主要看 ImplementationUtilities::CurrentHandleScope()。
static HandleScopeData* CurrentHandleScope() {
return &v8::HandleScope::current_;
}
v8::HandleScope::current_ 就是前面介绍的 HandleScope。
void HandleScopeImplementer::Iterate(
ObjectVisitor* v,
List<void**>* blocks,
ImplementationUtilities::HandleScopeData* handle_data) {
// Iterate over all handles in the blocks except for the last.
for (int i = blocks->length() - 2; i >= 0; --i) {
Object** block =
reinterpret_cast<Object**>(blocks->at(i));
v->VisitPointers(block, &block[kHandleBlockSize]);
}
// Iterate over live handles in the last block (if any).
if (!blocks->is_empty()) {
v->VisitPointers(reinterpret_cast<Object**>(blocks->last()),
reinterpret_cast<Object**>(handle_data->next));
}
}
Iterate 实现遍历全部的 Handle。接着看遍历时对对象的处理。具体实现在 MarkingVisitor。
void VisitPointer(Object** p) {
MarkObjectByPointer(p);
}
void MarkObjectByPointer(Object** p) {
Object* obj = *p;
if (!obj->IsHeapObject()) return;
MarkCompactCollector::MarkObject(HeapObject::cast(obj));
}
static inline void MarkObject(HeapObject* obj) {
// 没有标记则标记
if (!is_marked(obj)) MarkUnmarkedObject(obj);
}
void MarkCompactCollector::MarkUnmarkedObject(HeapObject* obj) {
// 打上标记
set_mark(obj);
// 押入栈,扫描它的子指针,类似遍历树
if (!marking_stack.overflowed()) {
marking_stack.Push(obj);
} else {
// Set object's stack overflow bit, wait for rescan.
// 溢出了则后面继续扫描
set_overflow(obj);
}
}
扫描和标记完全部对象后就进行清除。
void MarkCompactCollector::SweepSpaces() {
SweepSpace(Heap::old_space(), &DeallocateOldBlock);
}
static void SweepSpace(PagedSpace* space, DeallocateFunction dealloc) {
PageIterator it(space, PageIterator::PAGES_IN_USE);
while (it.has_next()) {
Page* p = it.next();
bool is_previous_alive = true;
Address free_start = NULL;
HeapObject* object;
// 遍历老生代的每一个对象,没有标记的则清除(回收内存)
for (Address current = p->ObjectAreaStart();
current < p->AllocationTop();
current += object->Size()) {
object = HeapObject::FromAddress(current);
// 如果标记,则先清除,然后回收(如果连续的内存则一起回收)
if (is_marked(object)) {
clear_mark(object);
if (!is_previous_alive) { // Transition from free to live.
dealloc(free_start, current - free_start);
is_previous_alive = true;
}
} else {
if (is_previous_alive) { // Transition from live to free.
free_start = current;
is_previous_alive = false;
}
}
}
if (!is_previous_alive) {
int free_size = p->AllocationTop() - free_start;
if (free_size > 0) {
dealloc(free_start, free_size);
}
}
}
}
接着看 dealloc 的回收逻辑。
void MarkCompactCollector::DeallocateOldBlock(Address start,
int size_in_bytes) {
// 清除记录集,记录集记录了老生代执行新生代的指针
Heap::ClearRSetRange(start, size_in_bytes);
Heap::old_space()->Free(start, size_in_bytes);
}
void Free(Address start, int size_in_bytes) {
int wasted_bytes = free_list_.Free(start, size_in_bytes);
accounting_stats_.DeallocateBytes(size_in_bytes);
accounting_stats_.WasteBytes(wasted_bytes);
}
int OldSpaceFreeList::Free(Address start, int size_in_bytes) {
FreeListNode* node = FreeListNode::FromAddress(start);
node->set_size(size_in_bytes);
int index = size_in_bytes >> kPointerSizeLog2;
node->set_next(free_[index].head_node_);
free_[index].head_node_ = node->address();
available_ += size_in_bytes;
needs_rebuild_ = true;
return 0;
}
这里的内存并不是直接释放,而是存到空闲链表,后续使用。可参考前面的图。