GC

常见GC垃圾回收算法

三种最基本的GC算法是标记-清除法、引用计数法、GC复制算法。其余很多是这几种的组合。

引用计数法:

计数器为0则回收。所以可即刻回收垃圾。

标记-清除法

全量标记,增量清除。遍历对象并标记,再对不可达对象收集到一个链表里进行回收。

GC复制算法

对象分配在from空间,from空间占满时,把这些对象复制到to空间,然后互换from空间和to空间。 所以不会有碎片化问题,也不需要挨个回收,但是对象会移动位置,且每次只能利用一半的堆空间。 GC复制算法的示意图

分代垃圾回收

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

增量式垃圾回收

标记清楚时的全量标记耗时又不能分帧,会造成卡顿。所以诞生了增量式GC。一般用三色标记法。

三色标记法是将对象根据搜索情况,分为三种颜色:

  • 白色:还未搜索过的对象
  • 灰色:正在搜索的对象
  • 黑色:搜索完成的对象

标记阶段就先标记灰色,再对灰色的子节点标记灰色,当前节点则标记黑色,依次下去。

但这样在并发时可能有问题,需要导致对象丢失,所以需要有屏障机制,一般会有写入屏障。

参考

https://zhuanlan.zhihu.com/p/690601125

UE的GC

基础算法

标记-清楚算法,分成三个部分:所有UObject先标记不可达,可达性分析,销毁垃圾。

垃圾收集:

  1. GC.MarkObjectsAsUnreachable: 标记所有对象不可达,保存所有Root对象。
  2. GC.PerformReachabilityAnalysisOnObjectsInternal: 遍历所有Root对象的引用对象,标识为可达。
  3. GatherUnreachableObjects: 遍历所有UObject,收集所有不可达对象到 GUnreachableObjects

不可达对象清除:

  1. UnhashUnreachableObjects:

    遍历所有不可达对象,调用ConditionalBeginDestroy,完成销毁前的清理工作,可自定义实现BeginDestroy。调用UnhashObject,从FUObjectHashTables移除。

    此处会提交贴图内存资源销毁操作到RHICommandList,由渲染线程执行销毁。

  2. IncrementalDestroyGarbage

UObject、继承FGCObject的对象可被 GC。通过UClass的反射信息可以在可达性分析时快速拿到对象的引用关系。

Cluster优化

如果一堆UObject生命周期与一个节点一致,则可以绑成一个 Cluster,这个节点则为 Cluster Root。可达性分析时每次遍历到这个 Cluster 时就可以认为整个 Cluster 里所有对象都可达。

AddToCluster 可以把一个UObject加入到一个 Cluster 里。

1
2
3
4
5
6
/**
* Adds this objects to a GC cluster that already exists
* @param ClusterRootOrObjectFromCluster Object that belongs to the cluster we want to add this object to.
* @param Add this object to the target cluster as a mutable object without adding this object's references.
*/
COREUOBJECT_API void AddToCluster(UObjectBaseUtility* ClusterRootOrObjectFromCluster, bool bAddAsMutableObject = false);

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

https://zhuanlan.zhihu.com/p/1922386751520997859

Licensed under CC BY-NC-SA 4.0