[Memory] Instantiated textures are not automatically GC-ed when re-allocated

Hi, I have a problem understanding Unity’s memory management.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Test : MonoBehaviour {
    Texture2D myTex;
    // Use this for initialization
    void Start () {
        for (int i = 0; i < 100; i++)
        {
            myTex = new Texture2D(1280,720);
        }
    }
 
    // Update is called once per frame
    void Update () {
     
    }
}

When you run the code above and check the profiler, there are 100 Texture2Ds on the memory and only 1 of them has a reference count of 1. Other 99 Texture2Ds has no references at all, but not GC-ed.

At first I thought this was a memory leak and submitted a bug report on this, but Unity QA replied that this was the intended behaviour. The reply was

I understand that the Texture2D is a ‘resource’, but the Texture2D above was instantiated on runtime, and there was no reference at all. I don’t understand why I have to explicitly destroy it before re-allocate it.

I would assume it’s because it’s a Unity asset type and the GC would have no way to differentiate between a Texture2D you generated and one that exists in the resources. You can call Resources.UnloadUnusedAssets() after you’re done this to free up the memory though (or call Destroy() on the Texture2D when you’re done with it)

Frequently instantiating new Texture2D objects and then forgetting about them isn’t really wise regardless of if the GC was collecting them or not, since that would result in bad GC spikes.

It’s better for performance to minimize loading of textures from disk to RAM frequently, thus having the default behavior be to not clear out currently unused textures from system memory is a better choice, as it is likely they may be used again quite soon and so the average dev will benefit from this behavior. And given that it’s usually more advanced users who are playing around with texture generation, the issue of memory overloading shouldn’t be hard to handle for most.

Yes, I agree with the most of what you have said. But

The GC doesn’t have to(and doesn’t need to) know what is what. All the GC should know is wether it is referenced or not.

I also tested like this :
Instantiate a gameobject prefab which has a renderer with a material, get its renderer.material (material instance is instantiated), change some of its value, and later destroy the instantiated gameobject. I thought the instantiated material would also be destroyed with it, but the profiler shows that it wasn’t.

Anyway the issue isn’t that hard to fix, but I don’t feel the concept is ‘clear’. Exactly what types are affected by this? Anything that can be created and saved in the Project? I have to double check when I need to re-initialize some instances or do something through script on runtime.

This has to do with how Unity’s set up. Unity is a c++ engine, with C# user scripts running on top of it. Everything that the engine knows about exists on the c++ side, and have C# wrappers that you work with. So things like Transforms, GameObjects, Materials, Textures, etc. all live in c++. Objects that you create at runtime - like strings, or custom classes - only exists in C#-land.

The rule-of-thumb is that if the type inherits from UnityEngine.Object, it lives in C++, and you’re handling a representation of it in your C# scripts.

How this ties into your question, is that Textures are not objects that can be GC’d, as they exist in the unmanaged memory of the c++ engine. The C# Texture type will (probably) get GC’d if you let go of all references to it, but that object’s just a link to the c++ Texture (and that’s the object you see in the profiler!). So you have to explicitly tell the engine to free it, either by destroying it directly (Object.Destroy), or by asking it to free all objects not referenced by C# scripts, which is done through Resources.UnloadAllUnusedAssets. That’s a bad method name, because since it’s a Resources-method, it seems like it’d only have to do with assets handled by the Resources system.

Note that for most of the engine objects, you don’t have to worry about this, as they’re removed either when you destroy them - usually though unloading the scene. But, objects that can be used across scenes (“Resources” in your reply from QA) won’t be handled in that way.

I don’t know all the details, but I’m guessing that the reason why Unity frees textures that’s stored to disk automatically is that they can just re-create them if you need them. Textures that you create manually are more tricky, so it’s probably not possible for Unity to clean them up automatically.

Finally, You can save yourself a lot of headache by just calling Resources.UnloadAllUnusedAssets whenever you’ve got a loading screen up anyways. It might be sufficient to not have to deal with this issue directly.

4 Likes

Thanks @Baste , you cleared a lot of things in my head. I really appreciate it!