• Bugs
  • [UE4] Memory leak when calling USpineSkeletonAnimationCompon

Related Discussions
...

Hello, there is a memory leak when calling SetAnimation(). The stack is like this.

0x00007ffaf76a0783 UE4Editor-Core.dll!FWindowsPlatformStackWalk::CaptureStackBackTrace() [...\Engine\Source\Runtime\Core\Private\Windows\WindowsPlatformStackWalk.cpp:364]
0x00007ffaf722fa42 UE4Editor-Core.dll!FMallocLeakDetection::Malloc() [...\Engine\Source\Runtime\Core\Private\HAL\MallocLeakDetection.cpp:466]
0x00007ffaf7232e55 UE4Editor-Core.dll!FMallocLeakDetection::Realloc() [...\Engine\Source\Runtime\Core\Private\HAL\MallocLeakDetection.cpp:538]
0x00007ffaf7233324 UE4Editor-Core.dll!FMallocLeakDetectionProxy::Realloc() [...\Engine\Source\Runtime\Core\Private\HAL\MallocLeakDetectionProxy.h:55]
0x00007ffaf723376d UE4Editor-Core.dll!FMemory::Realloc() [...\Engine\Source\Runtime\Core\Public\HAL\FMemory.inl:56]
0x00007ffabfd2f901 UE4Editor-SpinePlugin.dll!spine::Vector<float>::setSize() [...\SpinePlugin\Source\SpinePlugin\Public\spine-cpp\include\spine\Vector.h:82]
0x00007ffabfcf571d UE4Editor-SpinePlugin.dll!spine::CurveTimeline::CurveTimeline() [...\SpinePlugin\Source\SpinePlugin\Public\spine-cpp\src\spine\CurveTimeline.cpp:45]
0x00007ffabfcf717c UE4Editor-SpinePlugin.dll!spine::RotateTimeline::RotateTimeline() [...\SpinePlugin\Source\SpinePlugin\Public\spine-cpp\src\spine\RotateTimeline.cpp:48]
0x00007ffabfd238e4 UE4Editor-SpinePlugin.dll!spine::SkeletonBinary::readAnimation() [...\SpinePlugin\Source\SpinePlugin\Public\spine-cpp\src\spine\SkeletonBinary.cpp:978]
0x00007ffabfd2af06 UE4Editor-SpinePlugin.dll!spine::SkeletonBinary::readSkeletonData() [...\SpinePlugin\Source\SpinePlugin\Public\spine-cpp\src\spine\SkeletonBinary.cpp:333]
0x00007ffabfd011cb UE4Editor-SpinePlugin.dll!USpineSkeletonDataAsset::GetSkeletonData() [...\SpinePlugin\Source\SpinePlugin\Private\SpineSkeletonDataAsset.cpp:298]
0x00007ffabfcfd196 UE4Editor-SpinePlugin.dll!USpineSkeletonAnimationComponent::CheckState() [...\SpinePlugin\Source\SpinePlugin\Private\SpineSkeletonAnimationComponent.cpp:138]
0x00007ffabfd07206 UE4Editor-SpinePlugin.dll!USpineSkeletonAnimationComponent::SetAnimation() [...\SpinePlugin\Source\SpinePlugin\Private\SpineSkeletonAnimationComponent.cpp:213]

In our project we spawn an actor with random spine resource every time the player enters a specific map. SetAnimation() is called in BeginPlay() of the actor. If I keep switching between this map and other maps, this problem occurs.
I tried spawning actor with single spine resource, not random one of many resources, and I haven't reproduced the memory leak in this condition so far.
Does anyone know probable causes or solutions to the leak?

What Spine Runtimes version are you using? What UE4 version? Can you provide us with a minimal UE4 project that reliably reproduces the issue? What excat steps need to be taken and what settings need to be set for this to ocur?

16일 후

I'm using spine runtime 4.0 and ue4 4.27.2
I've been trying to reproduce it in an empty project but sorry that haven't succeeded.
Steps: Create several actor blueprints as ue4 runtime tutorial says, but with different spine resources. Call SetAnimation in beginplay event. Then spawn one of them randomly in level blueprint beginplay event. Then in PIE mode keep switching between this map and another map for about 10 minutes. Use mallocleak.start and mallocleak.stop to detect.


Hi, I tried to find the reason and here's my result.

  • In USpineSkeletonDataAsset::ClearNativeData(), spine runtime clears atlasToNativeData, deleting skeletonData and animationStateData.
  • In USpineSkeletonDataAsset::GetSkeletonData(), atlasToNativeData.Add() is called. The code is like this:
    /[i]...[/i]/
    skeletonData = xxx->readSkeletonData(...);
    /[i]...[/i]/
    if (skeletonData) {
       animationStateData = new (__FILE__, __LINE__) AnimationStateData(skeletonData);
       SetMixes(animationStateData);
       atlasToNativeData.Add(Atlas, {skeletonData, animationStateData});
    }
    
    In readSkeletonData() there's newly allocated memory.
  • I tried to add breakpoint in ClearNativeData(), and it is never hit when USpineSkeletonAnimationComponent is destroyed. It seems USpineSkeletonAnimationComponent.SkeletonData is never destroyed until the engine shuts down. So the memory allocated in GetSkeletonData() is not deleted. Maybe that's why llm detects memory leak.
    So is this a normal behavior? If so, as I hope to fix the memory leak detection, and unload the texture that SkeletonData references when we don't need spine animation, can I add SkeletonData->ClearNativeData() in USpineSkeletonAnimationComponent::FinishDestroy()? (Seems no memory leak any more after I tried to add it, but I'm not sure if there are any other problem)

Thanks, that's a great analysis. SkeletonData can be shared by multiple actors/USpineSkeletonAnimationComponents. As such, calling SkeletonData->ClearNativeData() in FinishDestroy() will potentially rip out data from under other actors that share the same skeleton data. I'd suggest not doing that unless you are sure that no other actor relies on that skeleton data.

USpineSkeletonDataAsset::BeginDestroy() is the method that is responsible for cleaning up the native spine-cpp memory allocated when parsing the skeleton data. It should be called when nothing references the USpineSkeletonDataAsset anymore as part of UE's garbage collection. Chances are, something is referencing an instanceof of USpineSkeletonDataAsset in one of your maps or globally, so the UE garbage collector never triggers, and the native memory is not released. That's not something we can work around on our end I'm afraid, but something you have to check in your specific setup.

Hi, thanks for reply.
I added break point in USpineSkeletonDataAsset::ClearNativeData and found that it is destroyed in UnhashUnreachableObjects(). It seems GC marked USpineSkeletonDataAsset as unreachable. I wonder what may cause this to occur?
I also checked the references of USpineSkeletonDataAsset in our project. Only some actor blueprints referenced it. These actor blueprints are destroyed when we want them to destroy, but the dataassets they reference are not destroyed.

Sorry, I don't quite follow. You say the skeleton data asset is both destroyed and not destroyed?

Regarding the destruction of the actors that reference the skeleton data asset: I'm not 100%, but I think UEs GC isn't deterministic like a simple reference counting algorithm. Meaning: the garbage collection of objects may be post-poned, even if they are not reachable anymore. If your app closes, before the GC does its final clean-up, the leak detector may tell you there's a leak. I can't find any definite information on that though. Ideally, UE would have an API to force garbage collection which could be called at the end of the program. This way we could verify this theory.

Hello, thanks and sorry for not being clear. I mean that the skeleton data asset is not destroyed when the spine skeleton animation component is destroyed. It is only destroyed when the engine shuts down. When I add breakpoint and then shut down the engine, I found it destroyed in UnhashUnreachableObjects().
Just now I also found that the skeleton data asset has RF_Standalone flag, which keeps object around for editing even if unreferenced. So it's good to keep it in editor. But as my memory report is given by android package, I'm still not sure why it is not destroyed at a right time in a non-editor package. I think we force GC every few minutes in our game(give me some time and I'll check it later) but the data asset is still not GCed.