4

Inspired from this question, was wondering what are the possible ways to create a memory leak in .Net. I once found one with ODBC Data Access. Has anyone had any experiences with latest version?

Edit: Are there any seemingly normal, harmless uses that can cause memory leaks?

Community
  • 1
  • 1
Mrchief
  • 75,126
  • 20
  • 142
  • 189

6 Answers6

4

The easiest way to create a memory leak is to misuse facilities designed for interop, since they deal with unmanaged memory.

For example, allocate a GCHandle pointing to an object, and never free it.

Edit: Are there any seemingly normal, harmless uses that can cause memory leaks?

Only one that I know of, particularly in some UI programs. It was possible in WinForms, but only recently became common due to WPF with MVVM:

The key point that many people overlook is that a delegate contains a reference to the object on which it runs.

So, if one is using a pattern such as MVVM with two-way bindings (implemented using events, comprised of delegates), and if the View is changed to a different View with the same ViewModel, by default the ViewModel's updates remain bound to both Views, which causes a leak of the old View.

This can in theory happen with any delegate, but in practice it's not common because the subscriber normally outlives the publisher or unsubscribes.

A similar situation exists when lambda event handlers are unsubscribed:

timer.Elapsed += (_, __) => myObj.Notify();
timer.Elapsed -= (_, __) => myObj.Notify();

In this case, even though the lambda expressions are identical, they represent different handlers, so the Elapsed event will still invoke Notify. The second line above has no effect; it doesn't unsubscribe, nor does it raise an error.

Note that improper unsubscriptions are usually caught during testing, so they seldomly cause "leaks" in released code. In contrast, the MVVM situation above causes no observable side effects (other than a memory and resource leak).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
2

GCHandle.Alloc() is a wonderful way to create "true" memory leaks in .NET. ("true" leak as in totally unreachable not reachable without hacks/reflection but still leaking)

EDIT

Edit: Are there any seemingly normal, harmless uses that can cause memory leaks?

"Seemingly normal" depends on ones knowledge/experience.

E.g. System.Windows.Forms.Timer "roots" itself while it's enabled (actually by means of GCHandle.Alloc()). If you add a Timer to a Form via Visual Studio's graphical editor, VS will

  • add a "components" collection to your class
  • generate code that adds the Timer to that "components" collection
  • generate code that disposes everything in the "components" collection in the Form's Dispose() method

That means things will work as expected, no leak.

But if you add the code that creates and starts the Timer yourself, it's easy to forget to add code that stops/disposes it. And Visual Studio won't (can't) do it for you.

In that case, the Timer will stay alive. It will never be collected. And it will keep running (and firing events). Even if the Form is closed/disposed.

And, since you would normally connect the Timers Tick event to some member function of the Form, the Form will also be kept alive. (Timer is rooted, Timer references event delegate, event delegate references Form.)

Since there are still many people who don't know or don't care about stuff like that, the code will look pretty "normal" to them.

Paul Groke
  • 6,259
  • 2
  • 31
  • 32
  • AFAIK they're not "totally unreachable". A little reflection will just get you to the static field that holds a reference to them, IIRC... – user541686 Jul 02 '11 at 04:36
  • Might well be. Also one could try calling `GCHandle.FromIntPtr()` on each possible `IntPtr` value :) – Paul Groke Jul 02 '11 at 04:50
  • Huh? Doing `FromIntPtr` requires a billion operations. Doing reflection requires a handful... not quite comparable, especially if you start talking about 64-bit... – user541686 Jul 02 '11 at 05:09
  • I didn't say `FromIntPtr` was comparable to using reflection. Also it wasn't a serious suggestion (although it certainly is doable). – Paul Groke Jul 02 '11 at 05:30
  • @pgroke: This is news to me. I haven't dealt with `Timer` at all but I'll keep this in mind. – Mrchief Jul 02 '11 at 22:59
1

If you read through all the answers provided in your link, they pretty much all apply to .Net as well. While the CLR and the JVM are completely different systems, they are still quite similar in the philosophy of their design (specifically, they are both managed systems), so they share many of the same strengths and pitfalls.

Ken Wayne VanderLinde
  • 18,915
  • 3
  • 47
  • 72
  • I thought so. Even then, I believe MS has done a good job in alleviating some of the JAVA issuesf or at least I think so. Isn't it? – Mrchief Jul 02 '11 at 22:56
  • I personally think that .Net is fundamentally a better system than the JVM (it has direct support for many things that the JVM does not. But as far as issues/problems go, I'm not sure that one has more/less than the other. They both do what they intended to do quite well. – Ken Wayne VanderLinde Jul 02 '11 at 23:16
1

Finalizer abuse can create a "memory leak", i.e. this would create an object whose memory can never be claimed by the GC:

public class Foo
{
    int[] value = new int[100];

    ~Foo()
    {
        GC.ReRegisterForFinalize(this);
    }
}
BrokenGlass
  • 158,293
  • 28
  • 286
  • 335
1

One pattern to exploit is a situation where a class has a private collection, which no one ever removes from, but someone keeps adding objects.

Say there is a background thread that's supposed to process elements from a static blocking collection and the thread dies, or blocks. Then the collection can only grow, causing a leak:

public class Test
{
    static Test()
    {
        Task.Factory.StartNew(() =>
        {
            Random r = new Random();

            try
            {
                while (true)
                {
                    object o = col.Take();

                    //process o fails at some point
                    if (r.Next(100) == 0)
                    {
                        Console.WriteLine("Fail! No one is processing anymore.");
                        throw new Exception();
                    }
                }
            }
            catch
            {
                Console.WriteLine("We caught the exception, but didn't resume processing");
            }
        });
    }

    private static BlockingCollection<object> col = new BlockingCollection<object>();

    public void Add(object o)
    {
        col.Add(o);
    }
}

class Program {
    public static void Main(string[] args)
    {
        Test t = new Test();
        while (true)
            t.Add(new object());
    }
}
Petar Ivanov
  • 91,536
  • 11
  • 82
  • 95
0
for (;;)
    Marshal.AllocHGlobal(0x400);
user541686
  • 205,094
  • 128
  • 528
  • 886