UE4 對象類型及內存管理(2)

在所有的對象保存和內存的問題中最重點的兩個問題就是:不同對象指針間的互相保存和UObject的GC機制。除去F Class的對象和智能指針的對象之外有自己的內存管理方式之外(上篇文章已經分析),UObject對象的內存管理完全交由GarbageCollection完成。

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;
...
};
FFixedUObjectArray:
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++;
			}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章