Why it is not garbage collected?

I am analyzing how garbage collector in UE works and can’t figure out one thing.

Suppose we have a very simple class member:

UCLASS()
class PROTOTYPE_API AMainCharacter : public APaperCharacter
{
	GENERATED_BODY()
	
	UPaperFlipbook* temp;
};

Now, when I initialize temp in a constructor, it is never cleaned up by a garbage collector:

AMainCharacter::AMainCharacter()
{
	temp = LoadObject<UPaperFlipbook>(this, TEXT("/Game/Sprites/MainCharacter/A_Move"));
}

When I do the same in a constuction script, it is cleaned up after time which is configured for collecting:

void AMainCharacter::OnConstruction(const FTransform & transform)
{
	temp = LoadObject<UPaperFlipbook>(this, TEXT("/Game/Sprites/MainCharacter/A_Move"));
}

Why does it work this way? I know the constructor is actually for CDO, but still, shouldn’t this variable be cleaned up as well?

I think I got this. I’ll describe what I’ve figured out, maybe someone will benefit from that.

In fact, a constructor is called several times (which is reasonable because of CDO). Of course this default object will be kept in memory and objects created there have a reference. Obviously, they will not be cleaned up by GC.

The small detail that escaped my notice is that LoadObject will always load the same object. When it is loaded once, it will return the same pointer on consecutive calls. That’s why I could not understand why it was not cleaned. Because there was only one instance, even the constructor was called several times!

Everything becomes clear when we change LoadObject to NewObject in a constructor. It will be called several times (depending a use case) and UPaperFlipbook will be instantiated several times. You can very easily check it iterating through FObjectIterator. When game starts, it contains references to multiple instances of UPaperFlipbook.

And now the most important part, after first round of garbage collector, we’ll end up with only one instance kept in memory (I assume it is the one used by Default Object). All other instances are gone. They were cleaned up automatically.

Here’s the method printing out what’s in memory:

void PrintObjects()
{
	for (FObjectIterator It(UPaperFlipbook::StaticClass()); It; ++It)
	{
		UObject* ObjectItem = *It;
		GEngine->AddOnScreenDebugMessage(-1, 5.f, FColor::Red, FString::Printf(TEXT("%s (%i)"), *ObjectItem->GetName(), ObjectItem->GetUniqueID()));
	}
}

Notice that UPaperFlipbook::StaticClass() creates additional instance of UPaperFlipbook with default name - this one should not be considered when debugging as it will be destroyed at the end of program execution.

If someone is curious how GC works in details, look at the sources.