1、实施GC的关键因素是由Object间的引用关系决定的UObject状态。我们先从上层分析UObject的引用关系和类型之间相互引用的注意事项:
A、UObject间的引用关系需要用指针强引用加UPROPERTY标签完成例如:
UCLASS(abstract, notplaceable, NotBlueprintable, HideCategories=(Collision,Rendering,"Utilities|Transformation"))
class ENGINE_API AController : public AActor, public INavAgentInterface
{
GENERATED_UCLASS_BODY()
private:
/** Pawn currently being controlled by this controller. Use Pawn.Possess() to take control of a pawn */
UPROPERTY(replicatedUsing=OnRep_Pawn)
APawn* Pawn;
...
};
UPROPERTY标签生成UProperty对象,UProperty对象可以控制对属性的访问等。也通过UProperty对象保存引用关系
不加UPROPERTY的如果想持久的保存指针的话,需要重写UObject的AddReferencedObjects接口为UObject对象添加引用。例如AActor类中为非UPROPERTY的成员变量OwnedComponents添加reference:
void AActor::AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
{
AActor* This = CastChecked<AActor>(InThis);
Collector.AddReferencedObjects(This->OwnedComponents);
#if WITH_EDITOR
if (This->CurrentTransactionAnnotation.IsValid())
{
This->CurrentTransactionAnnotation->AddReferencedObjects(Collector);
}
#endif
Super::AddReferencedObjects(InThis, Collector);
}
B、引擎中的代码UCLass类引用的非Object对象的变量比如UStruct UEnum 或者FVector等可是数据类型而定,如果是BlurprintType的类型;则需要写成UPROPERTY才能使用UObject的GC机制进行管理;目前BlurprintType的结构体,枚举都会被引擎编译成UStruct UEnum的对象。
C、F Class引用UObject的时候注意;为避免内存泄漏FClass应继承与FGCObject,并实现AddReferencedObjects接口,在接口中给UObject的指针添加reference例如FWidget中:
class FWidget
: public FGCObject
{
public:
...
private:
UMaterialInterface* TransparentPlaneMaterialXY;
UMaterialInterface* GridMaterial;
UMaterialInstanceDynamic* AxisMaterialX;
UMaterialInstanceDynamic* AxisMaterialY;
UMaterialInstanceDynamic* AxisMaterialZ;
UMaterialInstanceDynamic* CurrentAxisMaterial;
};
void FWidget::AddReferencedObjects( FReferenceCollector& Collector )
{
Collector.AddReferencedObject( AxisMaterialX );
Collector.AddReferencedObject( AxisMaterialY );
Collector.AddReferencedObject( AxisMaterialZ );
Collector.AddReferencedObject( OpaquePlaneMaterialXY );
Collector.AddReferencedObject( TransparentPlaneMaterialXY );
Collector.AddReferencedObject( GridMaterial );
Collector.AddReferencedObject( CurrentAxisMaterial );
}
D、智能指针一般不持久保存UObject的指针对象如果有需求我的习惯是使用TWeakObjectPtr的形式保存;可以用Isvalid先判断指针是否有效,但是因为是weakreference不对GC产生阻止的作用。
2、下面说下我对于UObject的GarbageCollection的理解,代码太多我不一定理解的全面。
A、先说最基本的UObject对象的内存空间的分配和释放;目前FUObjectAllocator处理UObject对象的内存分配和回收 不论上层多复杂但归根结底都要到关于内存的问题都会归到这里。比如我们runtime的时候执行destroy的操作,这时其实对象所占的空间依然存在,如果有指针引用到该对象的话我们可以看到内容为none;指针并不是nullptr;只有等执行了GC之后才会真正执行释放。同样UObject实例的时候会分配内存
GUObjectAllocator.FreeUObject(Object);
Obj = (UObject *)GUObjectAllocator.AllocateUObject(TotalSize,Alignment,GIsInitialLoad);
B、综上GC的最终目标其实就是对那些状态为Unreachable或者NoStrongReference等的Object对象执行销毁ConditionalFinishDestroy;另一个过程对已经执行销毁过程的Object释放内存FreeUObject。详见IncrementalPurgeGarbage在../Private\UObject\GarbageCollection.cpp
C、关于UObject的状态标记Flag
/** Objects flags for internal use (GC, low level UObject code) */
enum class EInternalObjectFlags : int32
{
None = 0,
// All the other bits are reserved, DO NOT ADD NEW FLAGS HERE!
ReachableInCluster = 1 << 23, /// External reference to object in cluster exists
ClusterRoot = 1 << 24, ///< Root of a cluster
Native = 1 << 25, ///< Native (UClass only).
Async = 1 << 26, ///< Object exists only on a different thread than the game thread.
AsyncLoading = 1 << 27, ///< Object is being asynchronously loaded.
Unreachable = 1 << 28, ///< Object is not reachable on the object graph.
PendingKill = 1 << 29, ///< Objects that are pending destruction (invalid for gameplay but valid objects)
RootSet = 1 << 30, ///< Object will not be garbage collected, even if unreferenced.
NoStrongReference = 1 << 31, ///< The object is not referenced by any strong reference. The flag is used by GC.
GarbageCollectionKeepFlags = Native | Async | AsyncLoading,
// Make sure this is up to date!
AllFlags = ReachableInCluster | ClusterRoot | Native | Async | AsyncLoading | Unreachable | PendingKill | RootSet | NoStrongReference
};
flag作为GC的状态保存在FUObjectItem中
D、FUObjectItem和FUObjectArray以及UGCObjectReferencer
FUObjectItem封装了单个的UObject指针在FUObjectArray中;FUObjectArray包含所有的live的Object对象
FUObjectItem:
/**
* Single item in the UObject array.
*/
struct FUObjectItem
{
// Pointer to the allocated object
class UObjectBase* Object;
// Internal flags
int32 Flags;
...
};
class FFixedUObjectArray
{
/** Static master table to chunks of pointers **/
FUObjectItem* Objects;
...
};
FUObjectArray:
class COREUOBJECT_API FUObjectArray
{
public:
...
//typedef TStaticIndirectArrayThreadSafeRead<UObjectBase, 8 * 1024 * 1024 /* Max 8M UObjects */, 16384 /* allocated in 64K/128K chunks */ > TUObjectArray;
typedef FFixedUObjectArray TUObjectArray;
...
TUObjectArray ObjObjects;
...
};
UGCObjectReferencer为非UClass的对象引用UObject对象指针提供了宿主;从而构成引用关系;GCObjectReference不依赖与任何对象因为设置了RootSet
void Init()
{
// Some objects can get created after the engine started shutting down (lazy init of singletons etc).
if (!GIsRequestingExit)
{
StaticInit();
check(GGCObjectReferencer);
// Add this instance to the referencer's list
GGCObjectReferencer->AddObject(this);
}
}
在执行GC CollectReference的过程中会把非UClass类的Uobject指针的引用关系指到GGCObjectReferencer上从而保证Uobject不会被清理。
E、分析GC的过程
入口CollectGarbageInternal GarbageCollection.cpp
/**
* Deletes all unreferenced objects, keeping objects that have any of the passed in KeepFlags set
*
* @param KeepFlags objects with those flags will be kept regardless of being referenced or not
* @param bPerformFullPurge if true, perform a full purge after the mark pass
*/
void CollectGarbageInternal(EObjectFlags KeepFlags, bool bPerformFullPurge)
{
...
};
关键的部分在ReachabilityAnalysis和执行IncrementalPurgeGarbage
ReachabilityAnalysis:
执行reachability分析的入口在CollectGarbageInternal 中
const double StartTime = FPlatformTime::Seconds();
FRealtimeGC TagUsedRealtimeGC;
TagUsedRealtimeGC.PerformReachabilityAnalysis(KeepFlags, bForceSingleThreadedGC);
UE_LOG(LogGarbage, Log, TEXT("%f ms for GC"), (FPlatformTime::Seconds() - StartTime) * 1000);
先执行MarkObjectsAsUnreachable把异常的ObjectItem状态修改:
ObjectItem->SetFlags(EInternalObjectFlags::Unreachable | EInternalObjectFlags::NoStrongReference);
正常的放进ObjectsToSerialize
然后执行CollectReferences ,遍历ObjectsToSerialize中的Object;从中取出ReferenceTokenStream
// Get pointer to token stream and jump to the start.
FGCReferenceTokenStream* RESTRICT TokenStream = &CurrentObject->GetClass()->ReferenceTokenStream;
然后遍历referenceTokenStream中的ReferenceInfo
FGCReferenceInfo ReferenceInfo = TokenStream->AccessReferenceInfo(ReferenceTokenStreamIndex);
再根据不同的referencetype执行ReferenceProcessor的HandleTokenStreamObjectReference
或者执行AddreferenceObject 构成ClusterReference
这步处理的结果会把一些ReferencedClusterRootObjectItem的EInternalObjectFlags::NoStrongReference | EInternalObjectFlags::Unreachable flag清理掉
ReferencedClusterRootObjectItem->ClearFlags(EInternalObjectFlags::NoStrongReference | EInternalObjectFlags::Unreachable);
IncrementalPurgeGarbage:
入口在CollectGarbageInternal 中:
if (bPerformFullPurge || GIsEditor)
{
IncrementalPurgeGarbage(false);
}
分为两个过程对那些状态为Unreachable或者NoStrongReference等的Object对象执行销毁ConditionalFinishDestroy;和另一个过程对已经执行销毁过程的Object释放内存FreeUObject。详见IncrementalPurgeGarbage
if (ObjectItem->IsUnreachable())
{
UObject* Object = static_cast<UObject*>(ObjectItem->Object);
// Object should always have had BeginDestroy called on it and never already be destroyed
check( Object->HasAnyFlags( RF_BeginDestroyed ) && !Object->HasAnyFlags( RF_FinishDestroyed ) );
// Only proceed with destroying the object if the asynchronous cleanup started by BeginDestroy has finished.
if(Object->IsReadyForFinishDestroy())
{
#if PERF_DETAILED_PER_CLASS_GC_STATS
// Keep track of how many objects of a certain class we're purging.
const FName& ClassName = Object->GetClass()->GetFName();
int32 InstanceCount = GClassToPurgeCountMap.FindRef( ClassName );
GClassToPurgeCountMap.Add( ClassName, ++InstanceCount );
#endif
// Send FinishDestroy message.
Object->ConditionalFinishDestroy();
}
else
{
// The object isn't ready for FinishDestroy to be called yet. This is common in the
// case of a graphics resource that is waiting for the render thread "release fence"
// to complete. Just calling IsReadyForFinishDestroy may begin the process of releasing
// a resource, so we don't want to block iteration while waiting on the render thread.
// Add the object index to our list of objects to revisit after we process everything else
GGCObjectsPendingDestruction.Add(Object);
GGCObjectsPendingDestructionCount++;
}
}
free的过程
FUObjectItem* ObjectItem = *GObjCurrentPurgeObjectIndex;
checkSlow(ObjectItem);
if (ObjectItem->IsUnreachable())
{
UObject* Object = (UObject*)ObjectItem->Object;
check(Object->HasAllFlags(RF_FinishDestroyed|RF_BeginDestroyed));
GIsPurgingObject = true;
Object->~UObject();
GUObjectAllocator.FreeUObject(Object);
GIsPurgingObject = false;
// Keep track of purged stats.
GPurgedObjectCountSinceLastMarkPhase++;
}