Though Unity’s managed memory system should (theoretically) reduce the burden on you, the intrepid game developer, there is still an awful lot to know and be aware of, especially when it comes to mobile development. Most PC titles made with Unity don’t need to worry about running out of memory because modern PCs have a bit to spare and can rely on virtual memory if pushed that far. On mobile devices, however, even a modestly populated Unity scene can start to crash under the stress of a few large textures and sound effects.
This was the position I found myself in recently, and I spent a chunk of time researching the topic. There wasn’t much documentation, and what I did find was scattered among several pages of documentation and questions on UnityAnswers. “Managed memory” sounds like your memory is automatically managed for you, but that isn’t the whole story; managed memory takes its cues from your actions in code to decide if it should allocate or free some memory. Doing the wrong things in your code can cause heaps of trouble and cause unneeded memory to stick around and take up space.
In this article, I’ll go over the different types of memory a Unity game uses, and some tips to optimize your usage in each area. I’ll also discuss how you can ensure that you only have the minimum of assets loaded at any one time, and how you can achieve dynamic loading/unloading of assets during gameplay.
Types of Memory in Unity
There are actually three areas (buried deep in the linked article) where memory is used by your game: application code, the managed heap, and the native heap.
Application code consists of the Unity engine, libraries, and all the code that you’ve written for your game. After it’s compiled into a binary and loaded onto a mobile device, this code will occupy a certain amount of space while your app is running. There isn’t really any way to “manage” this memory usage; all the application code is in memory at all times. Therefore, the only way to get savings here is to reduce the footprint of your application.
The managed heap is used by Mono, which is an open-source implementation of the .NET framework. This managed heap is used to create class instances in your (by using the “new” keyword, creating arrays of objects, or declaring variables). The “managed” concept comes into play because Mono should automatically increase or decrease the managed heap size to meet your memory needs. Mono will also “garbage collect” from time to time, which frees up any memory that was allocated, but no longer has any active references in code. One big mistake you can make is to accidentally keep a reference to memory you no longer need, stopping it from being garbage collected.
Finally, the native heap is a chunk of memory that Unity allocates from the operating system to store things like textures, sound effects, level data, etc. in memory. As far as I know, Unity uses its own take on managed memory to keep track of these assets; if a resource is needed in a level, it is loaded in, or unloaded if it is no longer referenced. This is very similar to Mono’s managed memory behavior, but it isn’t quite the same thing! Unity’s automatic resource loading and unloading can save effort on your part, but you also lose control over manually loading assets, which can lead to low-memory situations if you aren’t careful.
Optimizing Application Code Memory
This area is relatively simple to optimize, only requiring you to change a few project settings. Be wary of making these changes for existing games until you evaluate whether your game is using a feature that will no longer be available as a result of the optimizations.
When you load up a basic Unity game, it has a sizable overhead in the form of Mono libraries. Some of Unity’s player settings make it possible to reduce this overhead, at the expense of some functionality that Mono provides, but is rarely used in games. To access these settings, open Unity and go to “Edit > Project Settings > Player”. Here, under “Other Settings,” you’ll find a number of options:
The important ones are at the bottom, under “Optimization.” By setting the “API Compatibility Level” to “.NET 2.0 Subset”, you will be telling Unity to include fewer .NET libraries in your game. Beneath that, the “Stripping Level” tells Unity to further reduce the size of the overhead included in the build. Each stripping option will remove more and more functionality that you might need in your scripts, but reduce both the file size and memory usage of your application. Unfortunately, the ability to strip libraries is only available in Unity Pro.
Why would you not use the maximum stripping level possible? Your game may be making use of libraries that are not supported by “.NET 2.0 Subset” or a particular stripping level. If that is the case, your game will likely crash when you try to play it. One of the easier libraries to accidentally rely on is System.Xml, which provides XML support. As an alternative, you can use a lightweight, third-party XML parser instead (Unity suggests this one).
A list of supported libraries for each setting can be found here. Additionally, an explanation of the various stripping levels can be found on this page. In practice, I’ve found that a lot of the features that are stripped away are not common in game development, so it is certainly worth a shot to see if your project can benefit from this optimization.
Optimizing the Managed Heap
Unity recently put together an excellent help page with info about working with the managed heap. I’ll try to build off that a bit.
The managed heap deals with memory you allocate in your code – whether it be Boo, Unityscript, or C#. Whenever you allocate some space using the new keyword or the Instantiate() function (and “Instantiate” is really just calling “new” in turn), the required memory is allocated on the managed heap for all the variables and objects you need as a result. If there isn’t enough space, the heap size is increased – which can result in problems if you are running low on memory.
When you no longer need an instance of a class, there are generally no more references to the class in your scripts. At regular intervals, the Mono Garbage Collector will search through memory for objects that no longer have any active references and free that memory for you. In general, you do not want the garbage collector to run during gameplay because it will cause framerate spikes and unresponsiveness. This means that you must null references or Destroy() Unity objects to avoid memory leaks, but you should also be careful about doing it too much during gameplay, so you can avoid excessive garbage collection.
As it turns out, there are a lot of instances in games where you can call Destroy() too often. For example, if you are shooting cannon balls out of a cannon, you are instantiating memory and then throwing away memory for each cannon ball – very wasteful. A common and very helpful solution is to hide objects that you’d otherwise destroy in these situations and recycle them later. A recycling system isn’t always appropriate, but can be a great way to avoid garbage collection. See “Reusable Object Pools” in this bit of documentation.
Try to avoid unneeded calls to Destroy() or garbage collections during gameplay. You probably can’t remove all garbage collections (many will happen automatically despite your best intentions), but minimize them by performing garbage collection at the end of levels or when the player is unlikely to notice it (like on a pause screen). You can manually “suggest” that the garbage collector runs in C# with System.GC.Collect(). I say “suggest” because you don’t actually have control over garbage collection; you are merely telling the system that this would be a great time to perform a collection.
Optimizing the Native Heap
When you load up a Unity scene, all the assets in the scene (that is, textures, audio files, meshes) are pre-loaded into memory and the level is displayed to the player. When the level is finished, all the assets used in that level are then disposed of, unless the asset is also used by the next scene to be loaded up. In this way, Unity’s asset management is quite good – you know that everything you need for a scene will be in memory after the scene is loaded. There are, however, two things you can do to keep assets from being disposed of between scenes, and neither are necessarily bad things.
First, marking a GameObject or Component as DontDestroyOnLoad() will stop Unity from disposing of the GameObject, all child GameObjects, and all associated assets upon scene change. This is actually a great way to persist data between scenes (for keeping track of player progress, handling transitions between screens, etc). However, you should avoid keeping objects that are very asset heavy, or objects that maintain references to asset-heavy things.
The second thing to worry about is script references to scene objects. Most script references will be destroyed automatically when a scene change occurs, but if you have a GameObject or Component that is sticking around between scenes, and that it turn has a script with a reference to an audio asset or texture, you may be inadvertently keeping unneeded assets in memory. Another way to do this is if you keep a static reference to a class or component (as a singleton, for example) and the singleton is keeping references to assets.
If you have these references in your scripts, you should Destroy them when they will no longer be used. Unity can do this automatically for you, but if you have static references, you’ll need to either Destroy() or null them (or both) to get rid of the assets.
It is also important to note that when Unity automatically loads assets at the beginning of the scene, the only way to get it to clean up unused assets is to load a new scene or reload the current scene. This can prove problematic if you need to do some sort of asset streaming because your scene is too memory intensive.
Manual Control Over Asset Loading
It is also possible to load assets into your running scenes manually, but it requires a little more work. If you keep the assets in a folder called “Resources” (you can have multiple Resources folders in your project), then the assets will be available to load in manually using the Resources.Load(resourcePath) function. You can do this for textures, audio files, prefabs, materials, etc.
When you are done using a resource or several resources, you can then force Unity to free up that memory by calling Resources.UnloadUnusedAssets(). Note that for Unity to actually remove your assets, there must be no reference to the asset in your scripts (i.e. you have Destroy()‘d or null‘d the references). This command will also only unload assets that have previously been loaded with Resources.Load(). Any auto-loaded assets will remain until a scene change.