IOS: Heap increases when loading/unloading scenes until the game crashes

Hi everyone,

I need help with a crash that occurs in my game when it goes between two scenes a few times. After turning on Unity’s Internal Profiler script in XCode, I saw that the allocated heap increases quite steadily until the game finally terminates due to memory issues.

We have already taken some steps to avoid these kinds of crashes but they do not seem to be enough, which is the reason I come here for help.

What we’ve done so far:

  • When the game starts we keep hold of necessary objects by using DonDestroyOnLoad().

  • To clean up when going between the LevelSelect and our Level, we:

  • first load a completely empty scene with LoadSceneAsync(emptyScene, LoadSceneMode.Single)

  • call Resources.UnloadUnusedAssets();

  • call System.GC.Collect();

  • wait a frame

  • then load the next scene (Level or LevelSelect)

  • We even tried using a script that called System.GC.Collect(); every 30 frames without luck

But when looking at the info from the InternalProfiler, the heap keeps growing until the game runs out of memory and crashes. Here is an example of what the Internal Profiler spits out in a test run I made today.

Any ideas what one can do to prevent these kind of crashes?

Is this specific to the 2017.1 beta?

Else I thinks this best belongs in the “Editor & General Support” forum, or if iOS specific, the appropriate platform forum.

Hey Alex-Lian,
Probably not, but I am using the 2017.1.0f1. I just remembered last time I made a similar type of thread, I was told to create it in the beta forum so I assumed I should do the same this time.

Can you move this thread or do I need to create a new one in “Editor & General Support” ?

Well, it’d be nice to know if it was specific and if you do see it perhaps on 5.6 or not.
In which case, it does mean it is beta pertinent and we should fix it.

I can move the thread, but it seems we have the ambiguity of if it is a beta specific issue.

Yep, makes sense.

Just tested the game with a 5.6.1p2, which I had lying around in my computer, and the problem also appears there.

Also happens on 5.5.2p4.
Unable to go down to 5.4.x as it breaks the game.

Not sure if this applies to iOS since Unity uses IL2CPP, but I would assume the same principle applies here as it does in Mono.

So at a guess and assuming I’ve understood how Mono works, the problem you might be facing is that GC.Collect does not release/return memory to the system device, but simply marks it as unused and free to be used by future allocations.

The gotcha arises when due to your apps memory usage pattern the ‘free’ memory becomes fragmented. When this happens and there is no continuous amount of memory free for new allocations, Unity will grab a fresh new block from the system.

So for example lets say you grab a textures pixels into a byteArray for a 10MB texture. Unity will need to allocate a continuous block of 10MB for it. Later you destroy the byteArray after working on it and call gc.collect. Unity will mark that 10MB block of memory as being free.for future use, but its never returned to the system.

Now lets say you have a series of allocations for other work amounting to 5MB in total leaving 5MB continuous memory free ( though possibly not at the end of the block). Then you grab the bytearray of the textures pixels again. Best case Unity has to allocate an additional 5MB to provide a continuous 10MB block to store your byte array, worse case it allocates a whole new 10MB block, meaning you’ve now used 20MB in total!

Now repeat this process a few times or even every frame and its clear that eventually you will simply run out of memory.

The solutions are very much project dependent and especially its memory usage pattern. For that you’ll have to dig through your code and try and see what is going on. Various strategies exist such as object pooling, even as simple as if you know you are repeatedly going to need 10MB of ram for a byteArray to never destroying the reference to it and just re-use it.

Another option might be to go the ‘unsafe’ route ( unsure how that applies to IL2CPP ) and allocate memory yourself, as it is never in managed code, it never goes through garbage collection and so should be released back to the system, but that doesn’t always solve fragmentation issues. It just gives you a bit more leeway and control.

Of course alternatively there might be a bug in your code or Unity that is needless allocating memory every frame In which case you’ll have to track that down.

Finally one other important aspect to check is whether you might not be unloading assets that you think should be unloaded. Go grab the Unity MemoryProfiler from bitbucket. Read the documentation then try it on your project. Last time I used it, getting it to work was a bit hit or miss, but eventually you should be able to work it out.

What you might find is that static references can keep asset references alive when you think they would be unloaded. In fact its can be quite an eye opener to see just how easy a cascade effect of static references can end up causing a number of assets to never be unloaded. For example from memory I think if a gameObject contains several script components, and one of those uses a static reference then the entire gameObject is marked as static and so other references in other scripts on the gameObject can become marked as static too - at least it was something like that, or maybe use of ‘DontDestroyOnLoad’ affected all scripts on a gameObject.

I do remember being able to cut memory usage down by 5 - 10MB which on old 512Mb iOS devices using iOS8 and upwards could make all the difference between the app working or crashing.

Hope this helps.