views:

501

answers:

5

I have a C# Windows Service running on the .NET Framework 3.5 that is exhibiting a constantly growing number of GC Handles (seen using System Monitor on Windows Server 2003).

I have ensured that all resources are disposed of correctly, and have no Finalisers in my code.

The 'Large Object Heap size', and '# Bytes in all Heaps' are comparitively static, and I can see the '% Time in GC' is showing that Garbage collections are occurring.

The 'Private Bytes' counter is also increasing.

This symptom is causing my 'Memory Usage' in Task Manager to grow at around 35 MB per day, which is unacceptable as the Service is basically running a simple SELECT query against Oracle 10g and using .NET TraceSources every 5 seconds. It is probably worth mentioning that the TraceSource outputs to the Windows Event Log AND a text file using the .NET Listeners objects.

Does anyone know why the '# GC Handles' is constantly increasing, as I believe this is related to my increase in 'Memory Usage'?

+1  A: 

You're not releasing unmanaged resources being referenced by your code properly.

Are you familiar with the fixed statement? It can pin memory so that it can be accessed in an unsafe manner. However, there's another way to do that which we could argue is presumable unsafe.

var handle = System.Runtime.InteropServices.GCHandle.Alloc(myObject,
        System.Runtime.InteropServices.GCHandleType.Pinned);

The above type of code is exactly what could cause your type of problem. If you don't explicitly '.Free()' this pinned memory you'll not end up garbage collecting this object and you'll have a memory leak.

My guess is that a similar thing like this is happening with your Oracle 10g provider, unless you know you're doing something else which could potentially leak memory.

John Leidegren
A: 

Are you using ODP.Net ? Remember ODP.Net is not fully managed and has a native component as well. Do you use any other native resources, eg ActiveDirectory etc. Remember even if you have .Net assemblies it is a thin layer over COM for stuff like AD. I have seen massive memory leaks in AD code I used. I have not had memory issue with ODP.Net though and my company's enterprise service runs with ODP.Net.

To my knowledge we are not explicitly using the ODP.NET. We use whatever is installed with Visual Studio .NET 2008 and the Oracle 10.2.01 Oracle client
thehowler
System.Data.OracleClient 2.0.0.0
thehowler
+1  A: 

OK - sorted it.

After examining the source code for the .NET TraceSource class, it appears that, by design, it owns 2 lists of WeakReferences - 1 for the TraceSource objects, and one for the Switches.

It does this to support its RefreshAll method.

I had to use Reflection to manually clear out these lists. So, I cannot use the RefreshAll method safely anymore, but at least I don't have growth in Private Bytes and GC Handles any more.

To reproduce the problem, just create a whole load of TraceSource objects and close them - you can see the "leak".

thehowler
F.Y.I. I currently have an open MSDN Case with this issue and will endeavour to find out what the fix/workaround is.
thehowler
Received a workaround from Microsoft.If anyone needs to know how to work around it, let me know...
thehowler
Yes! I would be very much interested in that workaround as we use TraceSources heavily. Do you mind posting it here? Thanks!
Alex
Something like the answer below is the advice offered by Microsoft.
thehowler
+1  A: 

For what it's worth, we've fixed this issue in .NET 4.0 for both TraceSource and Switch.

We still consider it a best practice to not create a bunch of different instances of TraceSource (rather create one static instance per source and share them across threads) but in some cases this isn't possible where you're using a third party library that you can't fix.

Matt Ellis
Hi Matt,Thanks for the update.I think the only problem is that I can't find it stated in any Microsoft documentation that it's "a best practice to not create a bunch of different instances of TraceSource".
thehowler
Likely and oversight on our part. I'll work with the documentation folks to get something like this added to MSDN.
Matt Ellis
A: 
private void TidyWeakReferences(System.Diagnostics.TraceSource source) {

        try {
            // Clear down the Switch's WeakReferences
            TidyList(source, typeof(System.Diagnostics.Switch).GetFields(BindingFlags.NonPublic | BindingFlags.Static));
            // Clear down the Source's WeakReferences
            TidyList(source, typeof(System.Diagnostics.TraceSource).GetFields(BindingFlags.NonPublic | BindingFlags.Static));
        }
        catch { /* Nothing we can do here */ }
    }

private void TidyList(System.Diagnostics.TraceSource source, FieldInfo[] info) {

        List<WeakReference> list = null;

        foreach (FieldInfo fi in info) {

            if (fi.Name == "switches" | fi.Name == "tracesources") {

                list = (List<WeakReference>)fi.GetValue(null);
                lock (list) {
                    for (int i = list.Count - 1; i >= 0; i--) {
                        // Check to see if the GC has already collected these objects
                        if (!list[i].IsAlive) {
                            // It's dead, so remove it from the List (as .NET Framework 4.0 SHOULD fix.)
                            list.RemoveAt(i);
                        }
                    }
                }
            }
        }
thehowler