UE4 GC机制解析(零):概述

GC概述

Posted by bbkgl on June 8, 2021

春色三分

两分尘土

一分流水

GC 简介

通常,GC(Garbage Collection)是由编程语言在实现时,提供的一种自动管理资源生命周期的运行时机制。相对的有手动管理资源生命周期的机制,比如手动new/delete,malloc/free。也有一些自动管理,但并非强运行时(Runtime)的机制,比如RAII。其实可以像编程语言设计那样,按照抽象层次进行区分。所以按照抽象层次分级的话,则有:手动管理 < RAII < GC。同样的性能是正好反过来的:GC < RAII < 手动管理,抽象层次越高,运行时越明显,性能开销越大。

GC算法有很多,这里仅介绍标记-清除算法。

Writing a Mark-Sweep Garbage Collector – Dmitry Soshnikov

如上图,所有的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。

在清扫对象(垃圾)前,需要进行垃圾标记。垃圾标记的过程就是从根结点,遍历整个有向图,将遍历到的所有结点标记为可达(非垃圾)。于是在整个对象数组中,则认为不可达对象均为垃圾对象,需要清理。

最后遍历整个对象数组,清理垃圾。

image-20210609115045747

这里说明一下:以上几个过程只是我一家之言,并不是权威说法。只是觉得这样更符合直观,更容易理解。

UE4 GC的技术

前面文章中已经解析了UE4的反射实现,所以对于上面两个问题,反射基本能解决大半。

  • 反射可帮助获取对象成员信息
  • 并不是所有成员都需要记录在有向图上参与GC,基础类型如int,float等直接存储在对象的连续内存上,在回收对象内存时,这些基础类型成员就被一同回收。

所以问题转移,如何判断成员是否要挂在有向图上作为结点记录被引用呢?

后续通过源码进行详细解析,当然还有其它问题,包括簇,多线程GC,分帧GC等。