Understanding Memory Usage

I’ve tried looking around at different forum posts for an answer, but in my limited search was unable to find anything to answer my question on memory usage.

I’m noticing when I run a basic application, there is a constant cumulative effect on memory. As objects are added memory continues to be consumed but never shows any drop when those objects are then removed again.

I have a simple app that loads objects in the window and removes them as they leave the edge. The objects are added via…

SpriteManager.Instance.AddNewSprite((SpriteObject)Activator.CreateInstance(Type.GetType("myNamespace.SpriteObject")), SpriteList.Developer.BasicProjectile);

which then…

private List<SpriteObject> sprites;
...
public void AddNewSprite(SpriteObject obj, SpriteList.Developer spriteName)
{
    ObjectInfo objInfo = SpriteList.Instance.PullObjectInfo(spriteName.ToString());
    ImageInfo imgInfo = SpriteList.Instance.PullImageInfo(spriteName.ToString());
    addNewSprite(obj, objInfo, imgInfo);
}

private void addNewSprite(SpriteObject obj, ObjectInfo objInfo, ImageInfo imgInfo )
{
    obj.setID(NextSpriteID);
    NextSpriteID++;
    obj.Image.ObjectInfo = objInfo;
    obj.Image.ImageInfo = imgInfo;
    sprites.Add(obj);
    obj.LoadContent();
}

With the ensuing SpriteObject::LoadContent()…

public void LoadContent()
{
    Image.LoadContent();
}

and finally Image::LoadContent()…

public void LoadContent()
{
    ImageInfo.FontName = "Fonts/Arial";
    content = new ContentManager(
        ScreenManager.Instance.Content.ServiceProvider, "Content");
    if (ObjectInfo.texturePath != String.Empty)
        Texture = content.Load<Texture2D>(ObjectInfo.texturePath);

    ...

    renderTarget = new RenderTarget2D(ScreenManager.Instance.graphicsDevice,
        (int)imageDimensions.X, (int)imageDimensions.Y);
    ScreenManager.Instance.graphicsDevice.SetRenderTarget(renderTarget);
    ScreenManager.Instance.graphicsDevice.Clear(Color.Transparent);
    ScreenManager.Instance.spriteBatch.Begin();
    if (Texture != null)
        ScreenManager.Instance.spriteBatch.Draw(Texture, Vector2.Zero, Color.White);
    ScreenManager.Instance.spriteBatch.DrawString(font, ImageInfo.Text, Vector2.Zero, Color.White);
    ScreenManager.Instance.spriteBatch.End();

    Texture = renderTarget;
    ScreenManager.Instance.graphicsDevice.SetRenderTarget(null);
}

Then to remove the object when it reaches the edge of the screen…

private List<SpriteObject> sprites;
...
public void RemoveSprite(int spriteIndex)
{
    sprites[spriteIndex].UnloadContent();
    sprites.RemoveAt(spriteIndex);
}

SpriteObject::UnloadContent()…

public void UnloadContent()
{
    Image.UnloadContent();
}

Image::UnloadContent()

public void UnloadContent()
{
    content.Unload();
}

The objects are removed correctly from the List in the SpriteManager, and also vanish as their origin point touches the edge of the screen, however looking at the memory monitor in Visual Studio, as well as Windows Task Manager, the memory usage just keeps rising every time an object is created, and never falls again when it is removed.

I have some screenshots of the memory usage…


And if curious, here are the memory usage snapshots…

(Apparently I can’t post more then 2 links as a new user, which seems very limiting for support)

ID1
Obj: (https://)ibb.co/dyzNqn
Heapsize: (https://)ibb.co/dsp0bS

ID2
Obj: (https://)ibb.co/cV8Xqn (dif) (https://)ibb.co/cFUu37
Heapsize: (https://)ibb.co/iScQAn (dif) (https://)ibb.co/hJ2gi7

ID3
Obj: (https://)ibb.co/gEqHO7 (dif) (https://)ibb.co/b4QTVn
Heapsize: (https://)ibb.co/hXXWi7 (dif) (https://)ibb.co/mRecO7

ID4
Obj: (https://)ibb.co/b8zu37 (dif) (https://)ibb.co/eaMVbS
Heapsize: (https://)ibb.co/cDonO7 (dif) (https://)ibb.co/nHynO7

Whether I’m failing to read the memory analysis correctly or there’s something wrong with how the content is being removed, I’d greatly appreciate any insight. Thanks.

So I decided to do a longer test to see what would happen and ended up with this…

I guess eventually the GarbageCollection starts to kick in, only slightly after 1:15, but moreso after about 3:20 when it was pushing 400MB of memory, after which it consistently started freeing up memory in large chunks after it would rise for a bit.

I’m guessing this is some sort of efficient way to handle it, though if someone had some more insight on that, I am curious. It shows that it attempts gc many times throughout the run, but nothing really happens until it gets up near 400MB