V8垃圾回收分析
V8垃圾回收分析
关于垃圾回收机制
由于JavaScript 的对象是在V8上生产的,所以生命周期也是由V8来控制,这里就牵扯到了垃圾回收。任何垃圾回收都会做以下三件事情:
- 识别不再被引用的对象(Dead Objects)
- 回收这些对象所占用的内存
- 重新压碎整理碎片化的内存块(可选的)
分代机制
V8的垃圾回收机制也与其他主流语言一样(有垃圾回收机制的语言),使用的世代回收机制,主要有两个世代:新生代(Young generation)
和老年代(Old generation)
,V8在这个两个世代的前提下,又增加了独特的空间,用来存放专门的对象。以下是V8在内存管理中的所有空间:
RO_SPACE, // Immortal, immovable and immutable objects,
NEW_SPACE, // Young generation semispaces for regular objects collected with
// Scavenger.
OLD_SPACE, // Old generation regular object space.
CODE_SPACE, // Old generation code object space, marked executable.
MAP_SPACE, // Old generation map object space, non-movable.
LO_SPACE, // Old generation large object space.
CODE_LO_SPACE, // Old generation large code object space.
NEW_LO_SPACE, // Young generation large object space.
这些空间组成了V8的heap管理并且在Isolate初始化时完成各自空间的初始化。
在了解每个世代的垃圾回收之前,我们先看一下V8的heap结构:
新生代
新生代又分了两个SemiSpace(from和to),默认的初始化容量是1MB、最大容量8MB,这2个空间类似于Java中的Eden和Survivor区,这2个半区每次GC后,总有一个区是空的,为什么呢?
每次进行minor GC时,将from区中仍然存活的对象copy到to区,然后将from区的内存全部清除,然后设置两个区的page的flag,将FROM_PAGE设置为TO_PAGE,这样from区就有之有之前to区的存活对象,而to区经过交换数据后,没有存活的对象了,自然就空了。虽然新生代只有一半的空间在使用,但是新生代的对象,大部分生命周期都很短,这样做也是出于效率的考虑。用空间换时间,这样效率还是很高的。
那么V8内部是如何触发内存回收的呢?是监听Heap的内存大小?还是定期主动进行GC?我们通过代码一探究竟。
V8中的所有对象都是通过工程方法创建的。它有一个专门的工厂类(factory.h)用来创建所有的对象。
Handle<FixedArray> NewFixedArray(
int length, AllocationType allocation = AllocationType::kYoung);
Handle<PropertyArray> NewPropertyArray(
int length, AllocationType allocation = AllocationType::kYoung);
MaybeHandle<String> NewStringFromOneByte(
const Vector<const uint8_t>& str,
AllocationType allocation = AllocationType::kYoung);
MaybeHandle<String> NewStringFromUtf8(
const Vector<const char>& str,
AllocationType allocation = AllocationType::kYoung);
Handle<Struct> NewStruct(InstanceType type,
AllocationType allocation = AllocationType::kYoung);
Handle<ByteArray> NewByteArray(
int length, AllocationType allocation = AllocationType::kYoung);
Handle<Object> NewNumber(double value,
AllocationType allocation = AllocationType::kYoung);
Handle<JSObject> NewJSObject(
Handle<JSFunction> constructor,
AllocationType allocation = AllocationType::kYoung);
这里列出了主要使用的API,上述还有很多API没有列出,都是与创建V8对象相关的。每个对象的生成都会调用AllocateRawWithRetryOrFail
这个方法
HeapObject AllocateRawWithRetryOrFail(
int size, AllocationType allocation,
AllocationAlignment alignment = kWordAligned) {
AllocationResult alloc;
HeapObject result = AllocateRawWithLightRetry(size, allocation, alignment);
if (!result.is_null()) return result;
isolate()->counters()->gc_last_resort_from_handles()->Increment();
CollectAllAvailableGarbage(GarbageCollectionReason::kLastResort);
{
AlwaysAllocateScope scope(isolate());
alloc = AllocateRaw(size, allocation, alignment);
}
if (alloc.To(&result)) {
DCHECK(result != ReadOnlyRoots(this).exception());
return result;
}
// TODO(1181417): Fix this.
FatalProcessOutOfMemory("CALL_AND_RETRY_LAST");
return HeapObject();
}
AllocateRawWithRetryOrFail
会在调用AllocateRawWithLightRetry
和AllocateRaw
AllocateRawWithLightRetry->AllocateRaw->生成对象
AllocateRaw
会针对 不同的AllocationType
调用对应space生成对象。代码如下:
AllocationResult Heap::AllocateRaw(int size_in_bytes, AllocationType type,
AllocationAlignment alignment) {
DCHECK(AllowHandleAllocation::IsAllowed());
DCHECK(AllowHeapAllocation::IsAllowed());
DCHECK(gc_state_ == NOT_IN_GC);
#ifdef V8_ENABLE_ALLOCATION_TIMEOUT
if (FLAG_random_gc_interval > 0 || FLAG_gc_interval >= 0) {
if (!always_allocate() && Heap::allocation_timeout_-- <= 0) {
return AllocationResult::Retry();
}
}
#endif
#ifdef DEBUG
IncrementObjectCounters();
#endif
bool large_object = size_in_bytes > kMaxRegularHeapObjectSize;
HeapObject object;
AllocationResult allocation;
if (AllocationType::kYoung == type) {
if (large_object) {
if (FLAG_young_generation_large_objects) {
allocation = new_lo_space_->AllocateRaw(size_in_bytes);
} else {
// If young generation large objects are disalbed we have to tenure the
// allocation and violate the given allocation type. This could be
// dangerous. We may want to remove FLAG_young_generation_large_objects
// and avoid patching.
allocation = lo_space_->AllocateRaw(size_in_bytes);
}
} else {
allocation = new_space_->AllocateRaw(size_in_bytes, alignment);
}
} else if (AllocationType::kOld == type) {
if (large_object) {
allocation = lo_space_->AllocateRaw(size_in_bytes);
} else {
allocation = old_space_->AllocateRaw(size_in_bytes, alignment);
}
} else if (AllocationType::kCode == type) {
if (size_in_bytes <= code_space()->AreaSize() && !large_object) {
allocation = code_space_->AllocateRawUnaligned(size_in_bytes);
} else {
allocation = code_lo_space_->AllocateRaw(size_in_bytes);
}
} else if (AllocationType::kMap == type) {
allocation = map_space_->AllocateRawUnaligned(size_in_bytes);
} else if (AllocationType::kReadOnly == type) {
#ifdef V8_USE_SNAPSHOT
DCHECK(isolate_->serializer_enabled());
#endif
DCHECK(!large_object);
DCHECK(CanAllocateInReadOnlySpace());
allocation = read_only_space_->AllocateRaw(size_in_bytes, alignment);
} else {
UNREACHABLE();
}
if (allocation.To(&object)) {
if (AllocationType::kCode == type) {
// Unprotect the memory chunk of the object if it was not unprotected
// already.
UnprotectAndRegisterMemoryChunk(object);
ZapCodeObject(object.address(), size_in_bytes);
if (!large_object) {
MemoryChunk::FromHeapObject(object)
->GetCodeObjectRegistry()
->RegisterNewlyAllocatedCodeObject(object.address());
}
}
OnAllocationEvent(object, size_in_bytes);
}
return allocation;
}
老年代
老年代的对象都是经过了新生代一轮GC,晋升之后转移过来的。另外老年代又包含新生代的指针空间
和新生代数据空间
,前者是包含了指向新生代对象指针的对象,后者只保存原始数据对象,这些对象没有指向其他对象的指针。