[SOLVED] Memory leak

Between different scenes I dispose all the content (Textures, Input, Camera,…). But I experienced that the amount of used memory increases after every scene switch. So I tried to unload/load the same scene over and over again and profile it with the VS performance profiler to check where the leak comes from.

My result:
After every reload the memory consumption increaes by 2MB.
The amount of objects are always the same.
Screenshot 1
Left: Lanuch game
Right: 1st screen reload

Screenshot 2
Left: 1st screen reload
Right: 2nd screen reload

And it continues like that. The amount of objects are always the same, but I receive additional 2MB per reload. I use 2 contentManagers. The first receives the content on the application startup and never changes. The second one unloads and disposes all the content at a screen reload.

May this be a memory leak caused by monogame or am I missing something while reloading a scene?

Edit
If I do not “unload/Dispose” the contentManager, that problem does not appear. (Memory usage stays the same) Thats how I unload and dispose the contentManager
SceneResources.Unload(); SceneResources.Dispose(); SceneResources = null;

Thanks

Hello! I’m not sure I understand your post edit… Is your problem resolved now?

1 Like

No, the problem still exists. If I do not dispose the SceneResource the memory doesn’t increase.

But If I call:

SceneResources.Unload(); SceneResources.Dispose(); SceneResources = null;

which will result in reloading all the assets and lead to a higher memory usage.
But actually it should still use the exact same amount of memory, because the old resources are disposed.

Have you tried just using Content.dispose() ? (or whatever name your contentManager has)

1 Like

Yes, same result. I check at the sourcecode of MonoGame the ContentManager behaviour. .Dispose() will call .Unload() But anyway. Either way.Dispose() wont work

Without seeing more source, it’s difficult to see where the leak might be. Are all references to assets set to null or removed when the ContentManager is disposed?

1 Like

hmm… clearing the content manager, and clearing the content, are two different things, right?

Maybe you should simply clear your loaded assets. This will reduce memory consumption.

At least it works for me… I have my textures loaded into lists, and as I clear those lists, mem-use goes down.

1 Like

This is hard to tell, because the most objects get removed before I call contentManager.Dispose(); Only in some collections textures are left. But those textures are disposed after contentManager.Dipose has been called. And if those textures are disposed, I do clear all collections and reload everything again with the new contentManager.

They are still referenced in the contentManager.

Well, internally ContentManager uses a dictionary (hashtable) data structure with asset names as keys to prevent loading the same content multiple times. So by not unloading (or disposing) the ContentManager and “loading” the content again, you are effectively just getting the same reference of the previously loaded asset(s).

1 Like

Yes, thats clear. But why does it increase, if I dispose the content and load it again with another contentManager?

I suggest you get the trial for dotMemory and profile to see what is going on exactly. My guess is that there are probably some memory allocations to load and process the content from a file stream using the Content Pipeline. These allocations if no longer used would become “garbage” and are freed at the whim of the garbage collector. You could try to force garbage collection by calling GC.Collect and see if anything changes.

1 Like

I will give it a shot. Already tried GC.Collect and GC.WaitForPendingFinalizers();. It frees some memory, but just some KB.

We’re making guesses based on seeing three lines of code. There are several reasons this could be happening, but we can’t help much more without more context.

1 Like

Sorry, I did not want to post code on first place.

Basically I have 4 different kind of scenes. The player is able to travel from one place to another. Because in every scene other assets are used, I want to clear everything up before moving to the other scene. I am using static classes to share resources between gameobjects. Those classes work as follows:

public Texture2D this[Common common]
        {
            get
            {
                if (!sprites.ContainsKey(common))
                    sprites.Add(common, ResourceManager.SceneResources.Load<Texture2D>(GetPathToResource(common)));
                else if (sprites.ContainsKey(common) && sprites[common].IsDisposed)
                {
                    sprites.Remove(common);
                    return this[common];
                }

                return sprites[common];
            }
        }

The main ResourceManager which all the other static classes access:

public static class ResourceManager
{
    private static event Action sceneResourcesNoLongerAvailable;
    private const string ROOT_DIRECTORY = "Content";

    static ResourceManager()
    {
        CreateApplicationResourceManager();
        CreateSceneResourceManager();
    }

    public static ContentManager ApplicationResources { get; private set; }

    public static ContentManager SceneResources { get; private set; }

    public static event Action SceneResourcesNoLongerAvailable
    {
        add { sceneResourcesNoLongerAvailable += value; }
        remove { sceneResourcesNoLongerAvailable -= value; }
    }

    private static void CreateApplicationResourceManager()
    {
        if (ApplicationResources != null)
            return;
        
        ApplicationResources = new ContentManager(Game.Singleton.Content.ServiceProvider, ROOT_DIRECTORY);
    }

    public static void CreateSceneResourceManager()
    {
        if (SceneResources != null)
            CleanSceneResourceManager();

        SceneResources = new ContentManager(Game.Singleton.Content.ServiceProvider, ROOT_DIRECTORY);
    }

    public static void CleanSceneResourceManager()
    {
        SceneResources.Unload();
        SceneResources.Dispose();
        SceneResources = null;

        sceneResourcesNoLongerAvailable?.Invoke();
    }
}

If the player decides to go to another level, following will be called

private void DisposeLevel(Level level)
        {
            /* "Beautifully dispose all the level stuff */
            UserInterfaceManager.Singleton.RemoveAndDisposeAllControls();
            level.BackgroundLevel.Dispose();
            level.EntityManager.Dispose();

            /* Force to dispose the rest we may have missed because of my shit coding skills */
            ResourceManager.CleanSceneResourceManager();
            InputManager.Singleton.RemoveAllListeners();
            Camera.ResetPosition();
            Camera.Clean();

            /* Remove all the components */
            UserInterfaceManager.Singleton.Dispose();
            InputManager.Singleton.Dispose();

            Game.Singleton.Components.Remove(UserInterfaceManager.Singleton);
            Game.Singleton.Components.Remove(InputManager.Singleton);

            /* Call GC */
            Debug.WriteLine(GC.GetTotalMemory(true));
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Debug.WriteLine(GC.GetTotalMemory(true));
        }

        private void CreateLevel(Level level)
        {
            ResourceManager.CreateSceneResourceManager();
            Game.Singleton.Components.Add(new UserInterfaceManager(Game.Singleton));
            Game.Singleton.Components.Add(new InputManager(Game.Singleton));
            level.InitializeLevel(ResourceManager.SceneResources);
            level.FinalizeLevel();
        }

Also the old screen will be removed from the screenManager

    if (currentLevel != null) ScreenManager.RemoveScreen(currentLevel);

    var workThread = Task.Factory.StartNew(() =>
    {
        if(currentLevel != null)
            DisposeLevel(currentLevel);
        CreateLevel(nextLevel);
        ScreenManager.AddScreen(nextLevel);
        RemoveScreen(this);
    });

EDIT

It seems like after

        SceneResources.Unload();
        SceneResources.Dispose();
        SceneResources = null;

Nearly no memory has been released. I put a GC.GetTotalMemory before and after those lines, and the result is as following.

--------------------
MEM 20MB 21493KB
MEM 20MB 20994KB
--------------------
--------------------
MEM 32MB 33220KB
MEM 31MB 32721KB
--------------------
--------------------
MEM 43MB 44940KB
MEM 43MB 44441KB
--------------------
--------------------
MEM 55MB 56666KB
MEM 54MB 56167KB
--------------------
--------------------
MEM 66MB 68387KB
MEM 66MB 67888KB
--------------------

else if (sprites.ContainsKey(common) && sprites[common].IsDisposed)

Maybe this is the issue. If sprites aren’t retrieved they won’t be removed.

Well no, because the GC didn’t run yet and/or you still have references to your (disposed) objects (like the sprites). Disposing stuff doesn’t run the GC on it, it only cleans up unmanaged resources (and maybe sets references to null so the GC can easily collect)

1 Like

Thats not the case, because all those static resource classes do listen on disposed events and clear their own maps.

private static Dictionary<Common, Texture2D> sprites = new Dictionary<Common, Texture2D>();

public CommonResourceManager()
{
    ResourceManager.SceneResourcesNoLongerAvailable += ResourceManager_SceneResourcesNoLongerAvailable;
}

private void ResourceManager_SceneResourcesNoLongerAvailable()
{
    sprites.Clear();
}

I do force GC at the switch screen.

Thanks folks. I created a Windows project, copy + c + v the project files and used ANTS to search for the memory leak. It turned out that I was storing the Color of the textures in an static dictionary to save time.

private void TextureComponent_TextureChanged(Texture2D texture)
{
    if(texture == null) return;
    if (!colorBackup.ContainsKey(texture))
    {
        Color = new Color[texture.Width * texture.Height];
        texture.GetData(Color);
        colorBackup.Add(texture, Color);
    }

    Color = colorBackup[texture];
    Initialized = true;
}

Because the textures have another instance after a reload, the dictionary growed.

Thanks. Problem solved

2 Likes

Great! Mark the topic as solved maybe? I feel like we should remind each other to do that.

1 Like

Ok… How am I able to close that topic or mark it as solved? Cant find that button :cry:

Typically people add [SOLVED] before the title of the topic

1 Like