UObject
部分参考 InsideUE4 - UObject
UObject 是 UE 中所有 C++ 对象的根基类,它是 UE 对象系统的重要组成部分。通过 UObject,UE 支持以下功能:
- 垃圾回收
- 引用更新
- 映象
- 序列化
- 默认属性变化自动更新
- 自动属性初始化
- 自动编辑器整合
- 运行时类型信息可用
- 网络复制
- …
它有以下五个继承自 UObjectBase 的关键变量。
1/**
2 * 用于描述一个对象。比如
3 * - RF_ClassDefaultObject 表明它是 ClassDefaultObject;
4 * - RF_MarkAsRootSet 表明它不应被 GC,即使不被引用;
5 * - RF_NeedInitialization 表明该对象还未初始化完成状态;
6 * - ...
7 * 这些描述符体现为一个 32 位无符号整数的 bit
8 */
9EObjectFlags ObjectFlags;
10
11/**
12 * GUObjectArray 的索引,用于实现「内存管理」与「垃圾回收」。
13 * 其中 GUObjectArray 是一个全局 UObject 数组。
14 * 每构造一个对象,都会在构造函数中调用 AddObject() 将其加入到数组中。
15 */
16int32 InternalIndex;
17
18/** 该 Object 所属的 UClass,用于实现「反射」 */
19ObjectPtr_Private::TNonAccessTrackedObjectPtr<UClass> ClassPrivate;
20
21/** 该 Object 的名字,用于实现「反射」 */
22FName NamePrivate;
23
24/**
25 * 拥有该 Object 的 Object。
26 * 在运行时引用自己的对象可能有无穷个,但自己的 Outer 有且仅有一个。
27 * Outer 语义上就是真正拥有自己的那个对象。
28 */
29ObjectPtr_Private::TNonAccessTrackedObjectPtr<UObject> OuterPrivate;
后面四个对象都可以通过一个 public 接口 GetXXX() 获取。
UObject 的内存管理
UObject 与 UE 内存管理相关的所有逻辑是在其基类 UObjectBase 中实现的。
构造函数
首先观察 UObjectBase 的构造函数,发现执行了关键的一条函数 AddObject()。
1UObjectBase::UObjectBase(...)
2{
3 check(ClassPrivate);
4 // Add to global table.
5 AddObject(InName, InInternalFlags, InInternalIndex, InSerialNumber);
6}
7
8void UObjectBase::AddObject(FName InName, EInternalObjectFlags InSetInternalFlags,
9 int32 InInternalIndex, int32 InSerialNumber)
10{
11 ...
12 EInternalObjectFlags InternalFlagsToSet = InSetInternalFlags;
13 ...
14 GUObjectArray.AllocateUObjectIndex(this, InternalFlagsToSet,
15 InInternalIndex, InSerialNumber);
16 HashObject(this);
17}
这里主要关注其中两步,即 AllocateUObjectIndex() 和 HashObject()。
AllocateUObjectIndex()
这个函数的作用是将 Object 放到一个全局的名为 GUObjectArray 的对象中。顾名思义,该对象用于管理所有 UObject 的信息,之后要提的垃圾回收也是基于这个对象进行的。
1void FUObjectArray::AllocateUObjectIndex(UObjectBase* Object,
2 EInternalObjectFlags InitialFlags,
3 int32 AlreadyAllocatedIndex,
4 int32 SerialNumber)
5{
6 ...
7 int32 Index = INDEX_NONE;
8 LockInternalArray();
9
10 // 如果已经分配过了
11 if (AlreadyAllocatedIndex >= 0)
12 {
13 Index = AlreadyAllocatedIndex;
14 }
15 // 如果开启了忽略 GC 的优化
16 else if (OpenForDisregardForGC && DisregardForGCEnabled())
17 {
18 Index = ++ObjLastNonGCIndex;
19 // 如果超过 忽略 GC 的最大 Object 数量,则需要扩容。
20 if (ObjLastNonGCIndex >= MaxObjectsNotConsideredByGC)
21 {
22 Index = ObjObjects.AddSingle();
23 }
24 MaxObjectsNotConsideredByGC = FMath::Max(MaxObjectsNotConsideredByGC,
25 ObjLastNonGCIndex + 1);
26 }
27 // 剩下就是有 GC 的情况
28 else
29 {
30 // 这里用「空闲列表」是因为后续回收的 Object 索引是不确定的
31 // 要用空闲列表防止产生碎片
32 if (ObjAvailableList.Num() > 0)
33 {
34 Index = ObjAvailableList.Pop();
35 const int32 AvailableCount = ObjAvailableList.Num();
36 checkSlow(AvailableCount >= 0);
37 }
38 else // 如果空间不足,就扩容
39 {
40 Index = ObjObjects.AddSingle();
41 }
42 // 这个 check 信息量很大
43 // 可以得到 ObjFirstGCIndex > ObjLastNonGCIndex 总是成立的
44 // 这意味着 ObjectArray 划分为两部分,前面为 NonGC,后面为 NeedGC
45 // 同时 NonGC 会优先于 NeedGC 创建
46 // GC 过程不会访问 NonGC Object,它们也不能有指向 NeedGC 的引用
47 // 这样可以加快 GC 速度,因为只需遍历较少 Object。
48 check(Index >= ObjFirstGCIndex && Index > ObjLastNonGCIndex);
49 }
50 FUObjectItem* ObjectItem = IndexToObject(Index);
51 // 根据 Index 获取 ObjectArray 中的 Item,进行一些初始化工作
52 ...
53 UnlockInternalArray();
54
55 // 最后挨个通知订阅了 ObjectCreated 事件的 Listener
56 ...
57}
HashObject()
这个函数是利用 FName 的唯一性,求出 FName 的哈希值 Hash 后,建立 Hash 到 Object 的映射,从而实现根据名字访问对象。
1void HashObject(UObjectBase* Object)
2{
3 FName Name = Object->GetFName();
4 if (Name != NAME_None)
5 {
6 ...
7 int32 Hash = 0;
8
9 FUObjectHashTables& ThreadHash = FUObjectHashTables::Get();
10 FHashTableLock HashLock(ThreadHash);
11
12 // 因为一个 FName 唯一确定一个 Object
13 // 所以可以直接用 FName 进行 Hash
14 Hash = GetObjectHash(Name);
15 ...
16 ThreadHash.AddToHash(Hash, Object);
17 if (PTRINT Outer = (PTRINT)Object->GetOuter())
18 {
19 // 这次 Hash 会加入 Outer 的信息
20 Hash = GetObjectOuterHash(Name, Outer);
21 ...
22 ThreadHash.HashOuter.Add(Hash, Object->GetUniqueID());
23 // ObjectOuterMap(Outer).Add(Object)
24 AddToOuterMap(ThreadHash, Object);
25 }
26 // ClassToObjectListMap(Object.Class).Add(Object)
27 AddToClassMap( ThreadHash, Object );
28 }
29}
FreeUObjectIndex()
该函数和 AllocateUObjectIndex() 对应,但是只在析构函数中调用,作用是回收 GUObjectArray 中的 item。
1void FUObjectArray::FreeUObjectIndex(UObjectBase* Object)
2{
3 ...
4 // This should only be happening on the game thread (GC runs only on game thread when it's freeing objects)
5 check(IsInGameThread() || IsInGarbageCollectorThread());
6
7 // No need to call LockInternalArray(); here as it should already be locked by GC
8 int32 Index = Object->InternalIndex;
9 FUObjectItem* ObjectItem = IndexToObject(Index);
10 // 将 ObjectItem 重置回默认值
11 ...
12 if (Index > ObjLastNonGCIndex && !GExitPurge && bShouldRecycleObjectIndices)
13 {
14 ObjAvailableList.Add(Index);
15 }
16}
UObject 的创建
因为 UObject 将其所有构造函数都设为了 private,所以我们只能通过以下两种方式构造一个 UObject:
NewObject<ObjectClass>()CreateDefaultSubobject<ObjectClass>()
实际上内部都是将参数统一打包成一个 FStaticConstructObjectParameters 结构体,传入并调用 StaticConstructObject_Internal() 函数。
1UObject* StaticConstructObject_Internal(const FStaticConstructObjectParameters& Params)
2{
3 ...
4 UObject* Result = NULL;
5 // Subobjects are always created in the constructor,
6 // no need to re-create them unless their archetype != CDO
7 // or they're blueprint generated.
8 // If the existing subobject is to be re-used
9 // it can't have BeginDestroy called on it
10 // so we need to pass this information to StaticAllocateObject.
11 const bool bIsNativeClass = InClass->HasAnyClassFlags(CLASS_Native | CLASS_Intrinsic);
12 const bool bIsNativeFromCDO = bIsNativeClass &&
13 (
14 !InTemplate ||
15 (InName != NAME_None && (Params.bAssumeTemplateIsArchetype || InTemplate == UObject::GetArchetypeFromRequiredInfo(InClass, InOuter, InName, InFlags)))
16 );
17 const bool bCanRecycleSubobjects = bIsNativeFromCDO && (!(InFlags & RF_DefaultSubObject) || !FUObjectThreadContext::Get().IsInConstructor);
18
19 bool bRecycledSubobject = false;
20 Result = StaticAllocateObject(InClass, InOuter, InName, InFlags, Params.InternalSetFlags, bCanRecycleSubobjects, &bRecycledSubobject, Params.ExternalPackage);
21
22 // Don't call the constructor on recycled subobjects, they haven't been destroyed.
23 if (!bRecycledSubobject)
24 {
25 ...
26 (*InClass->ClassConstructor)(FObjectInitializer(Result, Params));
27 }
28 ...
29 return Result;
30}
这里的逻辑相对来说比较复杂。首先看最开始的三个 const bool。
首先看第一个 bIsNativeClass,其内容比较好理解,就是判断当前的类是不是 CLASS_Native/CLASS_Intrinsic 二者之一。前者代表这个类是在 C++ 而不是蓝图中定义的,而后者则表示该类是由 C++ 直接声明,未经过 UHT 生成反射代码,一般是引擎内部的类才会用到。
第二个 bIsNativeFromCDO 指的是是否根据**类默认对象(Class Default Object, CDO)**创建。
第三个 bCanRecycleSubobjects 指的是是否可以重复利用子对象(Subobject)。其中 RF_DefaultSubObject 标签只会在 CreateDefaultSubobject 时被赋予。也就是说如果一个对象不是通过 CDO 创建,且不是一个创建中的 Subobject,那么就可以对原来的 Subobject 进行重复利用。
至于为什么有「重复利用」一说,还得接着看后续的 StaticAllocateObject() 函数做了什么。
StaticAllocateObject()
这个函数实际做的工作是创建一个 Object 实例或者替换一个已经存在的 Class/Outer/Name 都一样的 Object。执行替换操作时,原 Object 将会被销毁,新 Object 将会占用其内存空间。
1UObject* StaticAllocateObject(...)
2{
3 ...
4 UObject* Obj = NULL;
5 if(InName == NAME_None)
6 { // 如果创建时没有设置 Name,则生成一个独一无二的 Name
7 ...
8 InName = MakeUniqueObjectName(InOuter, InClass);
9 }
10 else
11 {
12 // 查找是否存在 Outer Name 一样的 Object
13 Obj = StaticFindObjectFastInternal( /*Class=*/ NULL, InOuter, InName, true );
14 // 类型必须一致,否则 fatal
15 if (Obj && !Obj->GetClass()->IsChildOf(InClass)) { ... }
16 }
17
18 ...
19 if( Obj == nullptr )
20 { // 如果不存在上述 Object,就调用 AllocateUObject() 进行内存分配
21 ...
22 Obj = (UObject *)GUObjectAllocator.AllocateUObject(...);
23 }
24 else
25 { // 否则,直接替换
26 if (!bCreatingCDO && (!bCanRecycleSubobjects || !Obj->IsDefaultSubobject()))
27 { // 如果不是 CDO ,或者是蓝图类,那就要进行 Destory
28 if(!Obj->HasAnyFlags(RF_FinishDestroyed))
29 {
30 // 如果没完成 Destroy,则销毁原来的 Object
31 Obj->ConditionalBeginDestroy();
32 ...
33 Obj->ConditionalFinishDestroy();
34 }
35 ...
36 Obj->~UObject();
37 ...
38 }
39 else
40 {
41 bSubObject = true;
42 }
43 }
44 ...
45 if (!bSubObject)
46 { // 在此之前已经在这块内存上调用过析构函数,故需要重新格式化一下
47 // 并用 placement new 构造一个 Object
48 FMemory::Memzero((void *)Obj, TotalSize);
49 new ((void *)Obj) UObjectBase(...);
50 }
51 else
52 { // 反之,只需要修改原 Object 的一些变量即可,也就是进行了重用
53 Obj->SetFlags(InFlags);
54 Obj->SetInternalFlags(InternalSetFlags);
55 }
56 ...
57 return Obj;
58}
在 placement new 的过程中,就会执行 UObject 的构造函数,进而执行一些和 GUObjectArray 的交互行为。随着 return Obj; 的执行,至此,一个新的 UObject 就被创建好了。
UObject 的销毁
自动销毁
可以将指向 UObject 的指针置空,这样后续就会被 UE 自动回收掉
1Obj = NewObject<UObject>(this, TEXT("Obj"));
2Obj = nullptr;
主动销毁
UObject::ConditionalBeginDestroy()
异步执行且对象在当前帧内持续有效 等待下次GC Obj->ConditionalBeginDestroy(); Obj = nullptr; MarkPendingKill()
标记为PendingKill,等待回收。指向此实例的指针将设置为NULL,并在下一次GC时删除。 IsPendingKill 判断是否处于 PendingKill 状态 ClearPendingKill 清除 PendingKill 状态 Obj->MarkPendingKill(); Obj = nullptr; Engine\Config \BaseEngine.ini 更改下面参数,设置销毁时间间隔
gc.TimeBetweenPurgingPendingKillObjects=60 强制垃圾回收 UWorld::ForceGarbageCollection 弃用
GEngine->ForceGarbageCollection
GEngine->ForceGarbageCollection(true);