In UE 5.5, the fix applied by CL 36529858 in ue5-main updated USkeletalMeshComponent::ClearAnimScriptInstance() to call AnimScriptInstance::UninitializeAnimation() on the animation instance. This, in turn, uses UAnimInstance::GetProxyOnGameThread<FAnimInstanceProxy>() internally just to call Uninitialize() on the FAnimInstanceProxy. And this getter creates a new FAnimInstanceProxy when none exists. The problem is that the calls above can be made during garbage collection of the USkeletalMeshComponent, after the internal FAnimInstanceProxy has already been destroyed by the AnimScriptInstance. In that situation, the new FAnimInstanceProxy created by UAnimInstance::GetProxyOnGameThread<FAnimInstanceProxy>() is never destroyed and leaks.
Consider, for example, when a level containing an animated skeletal mesh is unloaded. In this scenario, UAnimInstance and USkeletalMeshComponent are destroyed in the same GC cycle. If UAnimInstance runs its BeginDestroy() method first, it destroys its internal FAnimInstanceProxy. Later, when USkeletalMeshComponent runs its own BeginDestroy() method, it calls USkeletalMeshComponent::OnComponentDestroyed(), which calls USkeletalMeshComponent::ClearAnimScriptInstance(), which - after the fix - calls AnimScriptInstance::UninitializeAnimation() and ends up creating a new dangling FAnimInstanceProxy.
Note: The licensee that reported this issue worked around it by protecting the call UAnimInstance::GetProxyOnGameThread<FAnimInstanceProxy>() inside AnimScriptInstance::UninitializeAnimation() against a null FAnimInstanceProxy.
1. Create a new project based on the Third Person template with C++
2. Compile and run from the debugger
3. Place an instance of '/Content/ThirdPerson/Blueprints/BP_ThirdPersonCharacter' in the level, and save
4. On the debugger, place breakpoints at the following locations:
4.1. UAnimInstance::UninitializeAnimation() [Engine\Source\Runtime\Engine\Private\Animation\AnimInstance.cpp:334]
4.2. FAnimInstanceProxy::~FAnimInstanceProxy() [Engine\Source\Runtime\Engine\Private\Animation\AnimInstanceProxy.cpp:117]
5. On the debugger, add watches for the following:
5.1. "this"
5.2. "AnimInstanceProxy"
6. Inside UE, double-click the level asset to reload it
6.1. Breakpoint (4.1) is hit inside USkeletalMeshComponent::OnUnregister() as the current world is destroyed
6.2. Breakpoint (4.2) is hit inside UAnimInstance::BeginDestroy() as the GC collects garbage
6.3. Breakpoint (4.1) is hit inside USkeletalMeshComponent::BeginDestroy() as the GC collects garbage
6.3.1. This call is made on the same skeletal mesh component as before, but this time "AnimInstanceProxy" was already destroyed and it is already null
6.3.2. As a result, the call "GetProxyOnGameThread<FAnimInstanceProxy>()" creates a new "AnimInstanceProxy" (step over on the debugger to see)
--> Breakpoint (4.2) is NOT hit again. The "AnimInstanceProxy" created above is never destroyed and leaks.
6.4. Breakpoint (4.1) is hit inside USkeletalMeshComponent::OnRegister() as the current world is loaded
6.4.1. This is called on a new UAnimInstance object with another new and unrelated FAnimInstanceProxy.
Note: The leak occurs if BeginDestroy() is called on UAnimInstance before USkeletalMeshComponent during garbage collection (steps 6.2 and 6.3 above). If the order is inverted, no leak occurs.
How does TextureRenderTarget2D get TArray<uint8> type data?
Why does the REMOVE method of map container remove elements have memory leaks?
How do I set a material as a post-processing material?
What is the difference between Camera and CineCamera?
What is the cause of the packaging error falling back to 'GameUserSettings' in ue5?
How to delete some elements correctly when deleting an array loop?
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-247262 in the post.