开发者

Creating a memory leak in C# or VB.Net

开发者 https://www.devze.com 2023-03-17 00:57 出处:网络
Inspired from this question, was wondering what are the possible ways to create a memory leak in .Net. I o开发者_运维知识库nce found one with ODBC Data Access. Has anyone had any experiences with late

Inspired from this question, was wondering what are the possible ways to create a memory leak in .Net. I o开发者_运维知识库nce 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?


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).


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.


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.


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);
    }
}


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());
    }
}


for (;;)
    Marshal.AllocHGlobal(0x400);
0

精彩评论

暂无评论...
验证码 换一张
取 消