8 Ways You can Cause Memory Leaks in .NET (2024)

Any experienced .NET developer knows that even though .NET applications have a garbage collector, memory leaks occur all the time. It’s not that the garbage collector has bugs, it’s just that there are ways we can (easily) cause memory leaks in a managed language.

Memory leaks are sneakily bad creatures. It’s easy to ignore them for a very long time, while they slowly destroy the application. With memory leaks, your memory consumption grows, creating GC pressure and performance problems. Finally, the program will just crash on an out-of-memory exception.

In this article, we will go over the most common reasons for memory leaks in .NET programs. All examples are in C#, but they are relevant to other languages.

Defining Memory Leaks in .NET

In a garbage collected environment, the term memory leak is a bit counter intuitive. How can my memory leak when there’s a garbage collector (GC) that takes care to collect everything?

There are 2 related core causes for this. The first core cause is when you have objects that are still referenced but are effectually unused. Since they are referenced, the GC won’t collect them and they will remain forever, taking up memory. This can happen, for example, when you register to events but never unregister. Let’s call this a managed memory leak.

The second cause is when you somehow allocate unmanaged memory (without garbage collection) and don’t free it. This is not so hard to do. .NET itself has a lot of classes that allocate unmanaged memory. Almost anything that involves streams, graphics, the file system or network calls does that under the hood. Usually, these classes implement a Dispose method, which frees the memory. You can easily allocate unmanaged memory yourself with special .NET classes (like Marshal) or with PInvoke.

Here are 8 of the most common offenders. The first 6 refer to managed memory leaks and the last 2 to unmanaged memory leaks:

1. Subscribing to Events

Events in .NET are notorious for causing memory leaks. The reason is simple: Once you subscribe to an event, that object holds a reference to your class. That is unless you subscribed with an anonymous method that didn’t capture a class member. Consider this example:

public class MyClass{ public MyClass(WiFiManager wiFiManager) { wiFiManager.WiFiSignalChanged += OnWiFiChanged; } private void OnWiFiChanged(object sender, WifiEventArgs e) { // do something }}

Assuming the wifiManager outlives MyClass, you have a memory leak on your hands. Any instance of MyClass is referenced by wifiManager and will never be allocated by the garbage collector.

Events are dangerous indeed and I wrote an entire article about it called 5 Techniques to avoid Memory Leaks by Events in C# .NET you should know.

So what can you do? There are several great pattern to prevent memory leaks from event in the mentioned article. Without going into detail, some of them are:

  1. Unsubscribe from the event.
  2. Use weak-handler patterns.
  3. Subscribe if possible with an anonymous function and without capturing any members.

2. Capturing members in anonymous methods

While it might be obvious that an event-handler method means an object is referenced, it’s less obvious that the same applies when a class member is captured in an anonymous method.

Here’s an example:

public class MyClass{ private JobQueue _jobQueue; private int _id; public MyClass(JobQueue jobQueue) { _jobQueue = jobQueue; } public void Foo() { _jobQueue.EnqueueJob(() => { Logger.Log($"Executing job with ID {_id}"); // do stuff  }); }}

In this code, the member _id is captured in the anonymous method and as a result the instance is referenced as well. This means that while JobQueue exists and references that job delegate, it will also reference an instance of MyClass.

The solution can be quite simple – assigning a local variable:

public class MyClass{ public MyClass(JobQueue jobQueue) { _jobQueue = jobQueue; } private JobQueue _jobQueue; private int _id; public void Foo() { var localId = _id; _jobQueue.EnqueueJob(() => { Logger.Log($"Executing job with ID {localId}"); // do stuff  }); }}

By assigning the value to a local variable, nothing is captured and you’ve averted a potential memory leak.

3. Static Variables

Some developers I know consider using static variables as always a bad practice. While that’s a bit extreme, there’s a certain point to it when talking about memory leaks.

Let’s consider how the garbage collector works. The basic idea is that the GC goes over all GC Root objects and marks them as not-to-collect. Then, the GC goes to all the objects they reference and marks as not-to-collect as well. And so on. Finally, the GC collects everything left (great article on garbage collection).

So what is considered as a GC Root?

  1. Live Stack of the running threads.
  2. Static variables.
  3. Managed objects that are passed to COM objects by interop (Memory de-allocation will be done by reference count)

This means that static variables and everything they reference will never be garbage collected. Here’s an example:

public class MyClass{ static List<MyClass> _instances = new List<MyClass>(); public MyClass() { _instances.Add(this); }}

If, for whatever reason, you decide to write the above code, any instance of MyClass will forever stay in memory, causing a memory leak.

Do you want a deeper dive into solving memory leaks? How about the skills to solve tough memory-related problems and improving performance? My new book Practical Debugging for .NET Developers might help you. Check out this short sample from the chapter on memory issues.

4. Caching functionality

Developers love caching. Why do an operation twice when you can do it once and save the result, right?

That’s true enough, but if you cache indefinitely, you will eventually run out of memory. Consider this example:

public class ProfilePicExtractor{ private Dictionary<int, byte[]> PictureCache { get; set; } = new Dictionary<int, byte[]>(); public byte[] GetProfilePicByID(int id) { // A lock mechanism should be added here, but let's stay on point if (!PictureCache.ContainsKey(id)) { var picture = GetPictureFromDatabase(id); PictureCache[id] = picture; } return PictureCache[id]; } private byte[] GetPictureFromDatabase(int id) { // ... }}

This piece of code might save some expensive trips to the database, but the price is cluttering your memory.

You can do several things to solve this:

  1. Delete caching that wasn’t used for some time
  2. Limit caching size
  3. Use WeakReference to hold cached objects. This relies on the garbage collector to decide when to clear the cache, but might not be such a bad idea. The GC will promote objects that are still in use to higher generations in order to keep them longer. That means that objects that are used often will stay longer in cache.

5. Incorrect WPF Bindings

WPF Bindings can actually cause memory leaks. The rule of thumb is to always bind to a DependencyObject or to a INotifyPropertyChanged object. When you fail to do so, WPF will create a strong reference to your binding source (meaning the ViewModel) from a static variable, causing a memory leak (explanation).

Here’s an example.

<UserControl x:Class="WpfApp.MyControl" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <TextBlock Text="{Binding SomeText}"></TextBlock></UserControl>

This View Model will stay in memory forever:

public class MyViewModel{ public string _someText = "memory leak"; public string SomeText { get { return _someText; } set { _someText = value; } }}

Whereas this View Model won’t cause a memory leak:

public class MyViewModel : INotifyPropertyChanged{ public string _someText = "not a memory leak"; public string SomeText { get { return _someText; } set { _someText = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof (SomeText))); } }

It actually doesn’t matter if you invoke PropertyChanged or not, the important thing is that the class derives from INotifyPropertyChanged. This tells the WPF infrastructure not to create a strong reference.

The memory leak occurs when the binding mode is OneWay or TwoWay. If the binding is OneTime or OneWayToSource, it’s not a problem.

Another WPF memory leak issue occurs when binding to a collection. If that collection doesn’t implement INotifyCollectionChanged, then you will have a memory leak. You can avoid the problem by using ObservableCollection which does implement that interface.

6. Threads that Never Terminate

We already talked about how the GC works and about GC roots. I mentioned that the Live Stack is considered as a GC root. The Live Stack includes all local variables and members of the call stacks in the running threads.

If for whatever reason, you were to create an infinitely-running thread that does nothing and has references to objects, that would be a memory leak. One example of how this can easily happen is with a Timer. Consider this code:

public class MyClass{ public MyClass() { Timer timer = new Timer(HandleTick); timer.Change(TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5)); } private void HandleTick(object state) { // do something }

If you don’t actually stop the timer, it will run in a separate thread, referencing an instance of MyClass, preventing it from being collected.

7. Not de-allocating unmanaged memory

Up to now, we only talked about managed memory. That is, memory that’s managed by the garbage collector. Unmanaged memory is a whole different matter – Instead of just avoiding unnecessary references, you will need to de-allocate the memory explicitly.

Here’s a simple example:

public class SomeClass{ private IntPtr _buffer; public SomeClass() { _buffer = Marshal.AllocHGlobal(1000); } // do stuff without freeing the buffer memory}

In the above method, we’ve used the Marshal.AllocHGlobal, which allocates a buffer of unmanaged memory (documentation). Under the hood, AllocHGlobal calls the LocalAlloc function in Kernel32.dll. Without explicitly freeing the handle with Marshal.FreeHGlobal, that buffer memory will be considered as taken in the process`s memory heap, causing a memory leak.

To deal with such issues you can add a Dispose method that frees any unmanaged resources, like so:

public class SomeClass : IDisposable{ private IntPtr _buffer; public SomeClass() { _buffer = Marshal.AllocHGlobal(1000); // do stuff without freeing the buffer memory } public void Dispose() { Marshal.FreeHGlobal(_buffer); }}
Unmanaged memory leaks are in a way worst than managed memory leaks due to memory fragmentation issues. Managed memory can be moved around by the garbage collector, making space for other objects. Unmanaged memory, however, is forever stuck in place.

8. Adding Dispose without Calling it

In the last example, we added the Dispose method to free any unmanaged resources. That’s great, but what happens when whoever used the class didn’t call Dispose?

One thing you can do is to use the using statement in C#:

using (var instance = new MyClass()){ // ... }

This works on IDisposable classes and translates by the compiler to this:

MyClass instance = new MyClass();;try{ // ...}finally{ if (instance != null) ((IDisposable)instance).Dispose();}

This is very useful because even if an exception was thrown, Dispose will still be called.

Another thing you can do is utilize the Dispose Pattern. Here’s an example of how you would implement it:

public class MyClass : IDisposable{ private IntPtr _bufferPtr; public int BUFFER_SIZE = 1024 * 1024; // 1 MB private bool _disposed = false; public MyClass() { _bufferPtr = Marshal.AllocHGlobal(BUFFER_SIZE); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // Free any other managed objects here. } // Free any unmanaged objects here. Marshal.FreeHGlobal(_bufferPtr); _disposed = true; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } ~MyClass() { Dispose(false); }}

This pattern makes sure that even if Dispose wasn’t called, then it will eventually be called when the instance is garbage collected. If, on the other hand, Dispose was called, then the finalizer is suppressed. Suppressing the finalizer is important because finalizers are expensive and can cause performance issues.

The dispose-pattern is not bulletproof, however. If Dispose was never called and your class wasn’t garbage collected due to a managed memory leak, then the unmanaged resources will not be freed.

Summary

Knowing how memory leaks can occur is important, but only part of the whole picture. It’s also important to recognize there are memory leak problems in an existing application, find them, and fix them. You can read my article Find, Fix, and Avoid Memory Leaks in C# .NET: 8 Best Practices for more info on that.

Hope you enjoyed the post, and happy coding.

8 Ways You can Cause Memory Leaks in .NET (2024)
Top Articles
Vip Lounge Odu
Craigslist Auto Parts Long Island
Use Copilot in Microsoft Teams meetings
Faridpur Govt. Girls' High School, Faridpur Test Examination—2023; English : Paper II
Monthly Forecast Accuweather
Kokichi's Day At The Zoo
Team 1 Elite Club Invite
CKS is only available in the UK | NICE
Mlifeinsider Okta
Sitcoms Online Message Board
Delectable Birthday Dyes
Walgreens On Nacogdoches And O'connor
Degreeworks Sbu
R/Altfeet
Directions To 401 East Chestnut Street Louisville Kentucky
Vanessa West Tripod Jeffrey Dahmer
Samantha Lyne Wikipedia
Trac Cbna
Zoe Mintz Adam Duritz
Site : Storagealamogordo.com Easy Call
Catherine Christiane Cruz
Https Paperlesspay Talx Com Boydgaming
Spn 520211
Amazing Lash Studio Casa Linda
Talkstreamlive
Gas Buddy Prices Near Me Zip Code
Best Town Hall 11
Rek Funerals
Mbi Auto Discount Code
Craigslist Free Puppy
Mississippi State baseball vs Virginia score, highlights: Bulldogs crumble in the ninth, season ends in NCAA regional
Final Exam Schedule Liberty University
Powerspec G512
KITCHENAID Tilt-Head Stand Mixer Set 4.8L (Blue) + Balmuda The Pot (White) 5KSM175PSEIC | 31.33% Off | Central Online
D3 Boards
SF bay area cars & trucks "chevrolet 50" - craigslist
Mcgiftcardmall.con
Vision Source: Premier Network of Independent Optometrists
Is The Nun Based On a True Story?
Blackwolf Run Pro Shop
Registrar Lls
The All-New MyUMobile App - Support | U Mobile
Hazel Moore Boobpedia
FREE - Divitarot.com - Tarot Denis Lapierre - Free divinatory tarot - Your divinatory tarot - Your future according to the cards! - Official website of Denis Lapierre - LIVE TAROT - Online Free Tarot cards reading - TAROT - Your free online latin tarot re
Ups Authorized Shipping Provider Price Photos
Rocket Lab hiring Integration &amp; Test Engineer I/II in Long Beach, CA | LinkedIn
Top 1,000 Girl Names for Your Baby Girl in 2024 | Pampers
Iupui Course Search
Hampton In And Suites Near Me
Tito Jackson, member of beloved pop group the Jackson 5, dies at 70
Is Chanel West Coast Pregnant Due Date
San Diego Padres Box Scores
Latest Posts
Article information

Author: Lilliana Bartoletti

Last Updated:

Views: 5451

Rating: 4.2 / 5 (73 voted)

Reviews: 80% of readers found this page helpful

Author information

Name: Lilliana Bartoletti

Birthday: 1999-11-18

Address: 58866 Tricia Spurs, North Melvinberg, HI 91346-3774

Phone: +50616620367928

Job: Real-Estate Liaison

Hobby: Graffiti, Astronomy, Handball, Magic, Origami, Fashion, Foreign language learning

Introduction: My name is Lilliana Bartoletti, I am a adventurous, pleasant, shiny, beautiful, handsome, zealous, tasty person who loves writing and wants to share my knowledge and understanding with you.