春色三分
两分尘土
一分流水
GC 简介
通常,GC(Garbage Collection)是由编程语言在实现时,提供的一种自动管理资源生命周期的运行时机制。相对的有手动管理资源生命周期的机制,比如手动new/delete,malloc/free。也有一些自动管理,但并非强运行时(Runtime)的机制,比如RAII。其实可以像编程语言设计那样,按照抽象层次进行区分。所以按照抽象层次分级的话,则有:手动管理 < RAII < GC。同样的性能是正好反过来的:GC < RAII < 手动管理,抽象层次越高,运行时越明显,性能开销越大。
GC算法有很多,这里仅介绍标记-清除算法。
如上图,所有的GC对象都被存放于一块空间内,然后按顺序执行标记和清除。
- 标记:遍历整个GC对象列表,根据某种规则,判断某个对象是否要清除,然后进行标记
- 清除:遍历列表,将标记清除的对象清除掉,回收内存
可以写出简单的伪代码:
mark(reachablelist):
while !isEmpty(reachablelist):
ref = remove(reachablelist)
for child in GetProperties(ref)
child = *propertyptr
if propertyptr != null && !isMarked(child)
setMarked(child)
add(reachablelist, child)
sweep(startref, endref):
objptr = startref
while objptr < endref
obj = *objptr
if isMarked(obj)
unsetMarked(obj)
else
free(objptr)
objptr = nextref(objptr)
按照如此简单的设计,会带来一些问题:
- 连续的内存中会出现越来越多的碎片(外部碎片?)
- 性能,比如stop the world
特别是stop the world,对于游戏来说简直是致命的,接下来对UE4的GC进行解析,尽管UE4的GC也存在很多不足。
UE4 GC概述
如果要实现标记-清除的垃圾回收机制?
试想,如果要实现类似于JVM的垃圾回收机制,从数据的收集开始,要设计并实现哪几个过程?
首先肯定是收集所有需要GC的对象的内存地址,这个过程需要在对象刚生成的时候进行,可以放在构造函数内(需要统一继承Object对象),或者是在某种统一的构造过程内进行。~~
其次是 GC对象有向图 的构造。在其他高级语言中,通常把无引用或者没有被任何指针指向的对象,视为需要被回收的对象,要能够查找到这些需要被回收的对象,则需要构造有向图。同时,在有向图中,也将这样的结点视为不可达结点或者是不可达对象。这里构造有向图时,存在两个问题:
- 对于每个对象,如何找到并记录成员的类型和地址?
- 是否要记录所有成员的信息?
这两个问题也是UE4 GC与其他高级语言GC机制的重要区别,其他语言通常有反射和虚拟机支持,获取这些信息相对简单,那主流的C++实现没有这些动态机制,又如何做到呢,先卖个关子2333。
在清扫对象(垃圾)前,需要进行垃圾标记。垃圾标记的过程就是从根结点,遍历整个有向图,将遍历到的所有结点标记为可达(非垃圾)。于是在整个对象数组中,则认为不可达对象均为垃圾对象,需要清理。
最后遍历整个对象数组,清理垃圾。
这里说明一下:以上几个过程只是我一家之言,并不是权威说法。只是觉得这样更符合直观,更容易理解。
UE4 GC的技术
前面文章中已经解析了UE4的反射实现,所以对于上面两个问题,反射基本能解决大半。
- 反射可帮助获取对象成员信息
- 并不是所有成员都需要记录在有向图上参与GC,基础类型如int,float等直接存储在对象的连续内存上,在回收对象内存时,这些基础类型成员就被一同回收。
所以问题转移,如何判断成员是否要挂在有向图上作为结点记录被引用呢?
后续通过源码进行详细解析,当然还有其它问题,包括簇,多线程GC,分帧GC等。