常见GC垃圾回收算法
三种最基本的GC算法是标记-清除法、引用计数法、GC复制算法。其余很多是这几种的组合。
引用计数法:
计数器为0则回收。所以可即刻回收垃圾。
标记-清除法
全量标记,增量清除。遍历对象并标记,再对不可达对象收集到一个链表里进行回收。
GC复制算法
对象分配在from空间,from空间占满时,把这些对象复制到to空间,然后互换from空间和to空间。
所以不会有碎片化问题,也不需要挨个回收,但是对象会移动位置,且每次只能利用一半的堆空间。

分代垃圾回收
把对象分为几代,将存活了一定次数的新生代对象当作老年代对象来处理,所以可以较少被gc;
新生代空间则可以用 GC 复制算法。

增量式垃圾回收
标记清楚时的全量标记耗时又不能分帧,会造成卡顿。所以诞生了增量式GC。一般用三色标记法。
三色标记法是将对象根据搜索情况,分为三种颜色:
- 白色:还未搜索过的对象
- 灰色:正在搜索的对象
- 黑色:搜索完成的对象
标记阶段就先标记灰色,再对灰色的子节点标记灰色,当前节点则标记黑色,依次下去。
但这样在并发时可能有问题,需要导致对象丢失,所以需要有屏障机制,一般会有写入屏障。
参考
https://zhuanlan.zhihu.com/p/690601125
UE的GC
基础算法
标记-清楚算法,分成三个部分:所有UObject先标记不可达,可达性分析,销毁垃圾。
垃圾收集:
- GC.MarkObjectsAsUnreachable: 标记所有对象不可达,保存所有Root对象。
- GC.PerformReachabilityAnalysisOnObjectsInternal: 遍历所有Root对象的引用对象,标识为可达。
- GatherUnreachableObjects: 遍历所有UObject,收集所有不可达对象到 GUnreachableObjects
不可达对象清除:
UnhashUnreachableObjects:
遍历所有不可达对象,调用ConditionalBeginDestroy,完成销毁前的清理工作,可自定义实现BeginDestroy。调用UnhashObject,从FUObjectHashTables移除。
此处会提交贴图内存资源销毁操作到RHICommandList,由渲染线程执行销毁。
IncrementalDestroyGarbage
UObject、继承FGCObject的对象可被 GC。通过UClass的反射信息可以在可达性分析时快速拿到对象的引用关系。
Cluster优化
如果一堆UObject生命周期与一个节点一致,则可以绑成一个 Cluster,这个节点则为 Cluster Root。可达性分析时每次遍历到这个 Cluster 时就可以认为整个 Cluster 里所有对象都可达。
AddToCluster 可以把一个UObject加入到一个 Cluster 里。
| |
UE5的GC改动
加入引用计数机制
主要是 StrongObjectPtr 使用,通过 RAII 在构造析构时操作 RefCount。可以减少可达性分析时间,同时单独针对StrongObjectPtr进行了优化。
增量式可达性分析
增量式的垃圾回收,目前还是Experimental阶段。
对于UE的GC的三个部分:所有UObject先标记不可达,可达性分析,销毁垃圾。最后销毁垃圾是可以分帧的,但是前两个部分是不能分帧的。而可达性分析阶段是耗时主要部分。
这里主要要考虑写入屏障,UE 引入 TObjectPtr,使得A.XX = B这种语句能被捕获到了。
参考
https://zhuanlan.zhihu.com/p/1991647341095179509
Unity的GC
基础
Unity 有两个脚本后端:Mono 和 IL2CPP (Intermediate Language To C++),它们各自使用不同的编译技术:
- Mono 使用即时 (JIT) 编译,在运行时按需编译代码。
- IL2CPP 使用提前 (AOT) 编译,在运行之前编译整个应用程序。
Unity的托管堆是游戏脚本(如C#代码)中所有引用类型对象(比如类实例、数组、字符串)存放的内存区域。它的生命周期由垃圾回收器自动管理,这也是“托管”的含义。
所以Unity的GC一般是指托管堆的这部分,采样 Boehm 算法。实际上是标记清楚算法,默认是增量式的,也就是有三色标记法、写入屏障等。
参考
https://docs.unity3d.com/cn/current/Manual/performance-managed-memory.html