每天学习亿点点day 11: UE4的垃圾回收机制和反射

1. 两种情况下property的指针会被空值化:

第一种是你的指针指向的actor或者actorcomponent已经用Uclass标记过,这个时候会对UE可见,也就会被UE的GC影响到

第二种就是你force delete一些editor里的asset这个时候指向这些asset的pointer也会被全部null化

2. 垃圾回收机制

虚幻实现垃圾回收机制,不再被引用或已被显式标记为销毁的`UObject`将定期清除。引擎构建一个引用图表以确定哪些`UObject`仍在使用,哪些是孤立的。在该图表根部是一组指定为"根集"的`UObject。任何`UObject`都可以添加到根集。当进行垃圾回收时,引擎将从根集开始,搜索已知`UObject`引用树来跟踪所有引用的`UObject。任何未被引用的`UObject`(意味着未在树搜索中找到这些对象)将被假设为不再需要,因此被删除。

一个实际的影响是,你通常需要保持对希望保持活跃的任何Object的`UPROPERTY`引用,或者将指向它的指针存储在`TArray`或其他引擎容器类中。Actor及其组件通常属于例外情况,因为Actor通常被链接回到根集的Object引用(例如它们所属的关卡),而Actor的组件被Actor自身引用。Actor可以显式标记为销毁,方法是调用它们的`Destroy`函数,这是从进行中游戏移除Actor的标准方法。组件可以使用`DestroyComponent`函数显式销毁,但它们通常在拥有它们的Actor从游戏中移除时被销毁。

虚幻引擎4中的垃圾回收速度快,效率高,内置大量的优化功能,能够尽量降低开销,如多线程可访问性分析可以标识孤立Object,优化的反加密代码能够尽快从容器中移除Actor。还有一些其他功能以调节,以更精准地控制如何以及何时执行垃圾回收,大部分都可以在 项目设置(Project Settings) 中的 引擎 - 垃圾回收(Engine - Garbage Collection) 下找到。

3. 反射和垃圾回收

什么是反射
反射是程序在运行时检查自身,将参与反射的数据(变量,类,函数等等)暴露给蓝图,允许运行时去调用。

反射的使用
宏就是用来帮助建立反射的,Unreal Header Tool(UHT)是建立反射的工具,它会扫描头文件。

例如:UE4中只要在一个函数的前面加上UFUNCTION()宏,然后在括号里面加上BlueprintCallable就可以在编辑器里面调用。

UStruct是所有 聚合结构体的基础类型(包含其它成员的类型,比如一个C++类、结构体、或者函数),不应该跟C++中的结构体(struct)混为一谈(那是UScriptStruct)。UClass可以包含函数、属性以及它们的孩子,而UFunction和UStriptStruct只能包含属性。

反射实现机制
Unreal Build Tool(UBT)和Unreal Header Tool (UHT)两个协同工作来生成运行时反射需要的数据。UBT属性通过扫描头文件,记录任何至少有一个反射类型的头文件的模块。如果其中任意一个头文件从上一次编译起发生了变化,那么 UHT就会被调用来利用和更新反射数据。UHT分析头文件,创建一系列反射数据,并且生成包含反射数据的C++代码(放到每一个模块的moulde.generated.inl中。注:最新版会生成到moudle.generated.cpp中),还有各种帮助函数以及thunk函数(每一个 头文件.generated.h)

用生成的C++代码来存储反射数据的一个最大好处就是,它可以保证跟二进制做到同步。你永远也不会加载陈旧或者过时的反射数据,因为它是跟引擎的其它代码同时编译的,并且它会在程序启动的时候使用C++表达式来计算成员偏移等,而不是通过针对特定平台/编译器/优化的组合中进行逆向工程。

生成的诸如StaticClass()、StaticStruct()函数是为了让当前类型更好的获取反射数据,以及那此转换函数(thunks)用来在蓝图或者网络复制中调用C++函数。这些必须声明为类或者结构体的一部分,这也就解释了为什么GENERATED_UCLASS_BODY() or GENERATED_USTRUCT_BODY()宏会包含在你的反射系统的类型中,而#include "TypeName.generated.h"的头文件中定义了这些宏。

UE4中的GC是追踪式、非实时、精确式,非渐近、增量回收(时间片),先标记后回收的过程,为了提高效率和减少回收过程中的卡顿,可以做到并行标记和增量回收以及通过簇来提高回收的效率等。

UObject和垃圾回收

UE4的垃圾收集比较自动化,通过垃圾回收便无需手动删除 UObjects,只需维持对它们的有效引用即可。类须派生自 UObject,才能启用垃圾回收。

在垃圾回收器中存在称为根集的概念,此根集是一个对象列表,回收器不会对这些对象进行垃圾回收。只要根集中的对象到讨论中的对象之间存在引用路径,对象便不会被垃圾回收。如对象到根集的此路径不存在,它便会被识别为无法达到,垃圾回收器下次运行时便会将其收集(删除),引擎以特定间隔运行垃圾回收器。
怎样才算是一个引用?存储在一个UPROPERTY属性中的UObject 对象指针。让我们看看简单的例子:

void CreateDoomedObject() { MyGCType* DoomedObject = NewObject<MyGCType>(); }

当我们调用上面的方法时,我们实例化一个新的UObject对象,但我们没有存储对象指针到任何UPROPERTY的属性中,所以它不是根集的一部分。事实上,垃圾回收器会检测这个对象是否无法访问,若是,则回收它。

UE4采用了标记-清扫垃圾回收方式,是一种经典的垃圾回收方式。一次垃圾回收分为两个阶段。第一阶段从一个根集合出发,遍历所有可达对象,遍历完成后就能标记出可达对象和不可达对象了,这个阶段会在一帧内完成。第二阶段会渐进式的清理这些不可达对象,因为不可达的对象将永远不能被访问到,所以可以分帧清理它们,避免一下子清理很多UObject,比如map卸载时,发生明显的卡顿。

Actor和垃圾回收

Actors 通常不会被垃圾回收。Actors 生成后,必须手动调用 Destroy()。它们不会被立即删除,而会在下个垃圾回收阶段被清理。

 

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()
 
public:
    UPROPERTY()
    MyGCType* SafeObject;
 
    MyGCType* DoomedObject;
 
    AMyActor(const FObjectInitializer& ObjectInitializer)
        :Super(ObjectInitializer)
    {
        SafeObject = NewObject<MyGCType>();
        DoomedObject = NewObject<MyGCType>();
    }
};
 
void SpawnMyActor(UWorld* World, FVector Location, FRotator Rotation)
{
    World->SpawnActor<AMyActor>(Location, Rotation);
}

  

调用上述函数时,将在世界场景中生成一个actor。Actor的构建函数创建两个对象。一个指定到 UPROPERTY,另一个指定到裸指针。Actors自动成为根集的一部分,SafeObject将不会被垃圾回收,因为它从根集对象出到达。然而 DoomedObject的进展不是十分顺利。我们未将其标为UPROPERTY,因此回收器并不知道其正在被引用,而会将它逐渐销毁。

posted @ 2021-05-31 12:00  Tonarinototoro  阅读(652)  评论(0编辑  收藏  举报