Author Topic: Memory Leaks in languages with garbage collectors (C#)  (Read 5711 times)

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1015
Memory Leaks in languages with garbage collectors (C#)
« on: February 01, 2020, 06:44:39 PM »
I have been working in C# again recently, which is partly to blame for my lack of contributions within the Outpost 2 code base. Even when I go decent lengths of time not programming in C#, it comes back easy, and is much quicker to work in than C++.

In the past, I had assumed languages like C# eliminated memory leaks through automatic disposal of unreferenced memory allocations (garbage collector). As a side note, I've been pretty impressed with how mature garbage collectors have become over the years. There are certainly still best practices that should be observed coding in this sort of environment though.

The project I am working on contained a memory leak that began to affect performance after running for about 40 minutes. I have never profiled a moderately sized project to diagnose and fix a memory leak before, so it was a good experience (the good experience part is probably colored by the fact that I was successful without too much difficulty in fixing the memory leak).

There are some basic profiling tools built into the free Visual Studio IDE which I had seen but never seriously used. Visual Studio includes a graph of memory allocation over time, CPU usage, and a graph related to event handling that I don't quite understand. It also shows how often the garbage collector is called. You can take memory snapshots and then sort the contents of the snapshot based on largest footprint or by largest number of instances.

So, you can compare snapshots over time and see the memory usage increase or decrease. You can then sort to see where more memory is being allocated over time. This was enough to then review the source code of the offending module and fix.

There are several paid .not profilers and Visual Studio Enterprise has expanding features for profiling. I was pleasantly surprised with how robust the out of the box free profiler was though. Especially for hobby use it seems good enough.

So moral of the story for me was you still have to understand what memory leaks are in a language like C#. While the garbage collector handles many of the mundane circumstances where memory management would be forefront in the design in a language like C++, it is still possible to get in trouble in C#. I was recently reading events are notorious for causing memory leaks in C# programs when not used properly.

Well I guess enough rambling for now...

-Brett

Offline TechCor

  • Full Member
  • ***
  • Posts: 141
Re: Memory Leaks in languages with garbage collectors (C#)
« Reply #1 on: February 02, 2020, 03:04:56 AM »
Yay, activity on the forum!

In the past, I had assumed languages like C# eliminated memory leaks through automatic disposal of unreferenced memory allocations (garbage collector).
It does. You didn't explain what was happening in your particular situation, but...

There a couple things to consider:

First, you can cause memory to balloon if you keep references but forget to remove them. The event handler is one such situation because the dispatcher will hold a reference to the object (so that it can call the listener!). When the listener is called, you will have the opportunity to unregister it, so it hasn't technically leaked.

You could also have a situation with something like a list or dictionary where you keep adding objects, but forget to remove them.

Second, the GC doesn't collect every time memory loses all references. There is an algorithm that determines when it should collect.

So, let's say you have an update loop that is called every frame with a memory allocation of some sort. In most cases, the allocation is small enough or localized in scope that you won't have any memory issues, but for larger allocations, you will see memory slowly grow with each frame. What is happening is that the unreferenced memory has not yet been garbage collected. The result is you will see memory "spikes" in the graph as it collects every once in a while. For performance sensitive / real-time applications (like games), the large collection can cause intermittent pauses. In this case, you will want to cut back on such large and frequent allocations. The recommended practice would be to cache a reference and clear the data rather than allocate every frame. DotNetMissionSDK does this for the StateSnapshot class, which includes a huge allocation for the TileMap.

Traditionally, I wouldn't call any of these things memory "leaks" as that usually refers to memory that is allocated but no longer accessible. Since you have lost all references, you can't use or free the memory - it has leaked out of the program!

Memory management in C# is easier, but you still have to know what you are doing. Keep track of those references and minimize allocations!

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: Memory Leaks in languages with garbage collectors (C#)
« Reply #2 on: February 02, 2020, 08:03:18 AM »
I was starting to wonder where you had disappeared to. ;)

Any specifics on what the bug was, or what the solution was? Also kind of curious about the debugger, in terms of what information is gave, or how you navigated it to find the bug. Screenshots welcome. :P

Offline Vagabond

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 1015
Re: Memory Leaks in languages with garbage collectors (C#)
« Reply #3 on: February 08, 2020, 09:44:09 AM »
Hooman,

Below is the real time memory graph provided by Visual Studio. It is a screenshot of an application starting up. You can use this to see how often the garbage collector is called. In this case, it was called twice during startup and then further object creation went basically to zero.



The next screenshot shows a series of memory snapshots. While the application is running, you can record a snapshot and then compare the delta between snapshot of how many objects are in memory and the overall memory footprint.



The final screenshot shows a detail view of a snapshot. You can sort this based on most objects, largest object, or object count * object size. I cut the object names off from the left, but you should get the point.



By taking snapshots over a period of time, I was able to see a particular object count start to increase and never be reclaimed by the garbage collector. I was then able to go into the source code and review how that object was created and stored.

The object in question was removed from a queue. After removal, a portion of the object was used to create a new object of the same type and add it to a list. Somewhere in this process, a reference must have remained to the original object and it was never disposed of. After improving the logic a bit in this section, everything worked out. This was a bit of trial and error on my part, so hopefully it makes some sense...

Anyways, I'm pretty impressed with the profiler built into Visual Studio. It sure made diagnosing this issue easy for me. Several articles mentioned the Enterprise license came with a better profiler, although I probably don't have the skill to notice a difference.
« Last Edit: February 08, 2020, 09:45:44 AM by Vagabond »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: Memory Leaks in languages with garbage collectors (C#)
« Reply #4 on: February 09, 2020, 01:26:27 PM »
Hmm, neat. Clearly the debugging tools have been getting better over the years.

Some of that process reminds me of memory editors that let you search for values in game memory for cheating. They let you take a snapshot and compare with previous snapshots, finding values that were the same as before, or have increased or decreased since the previous search.

I suppose those new debugging tools would be good for finding inefficient use of memory, even if it's not a leak. Could help you memory optimize code.