none
Memory leak in WMI when querying event logs

    Question

  • I have encountered an obvious memory leak when monitoring the Windows event logs for new events using the WMI.

    Here's the parameters of my test:

    * Windows Vista Ultimate SP1 32-bit with the latest updates
    * Visual Studio 2008 C#
    * Windows application using .NET 2.0

    I have written a very simple C# program that subscribes to the Win32_NTLogEvent WMI object and receives notifications of new events written to the Application event log. Each time an event notification is received, the memory used by my app grows by 4-8K, as displayed by Task Manager. This memory is never reclaimed unless the app is unloaded. Otherwise, the app works exactly as expected. There are no errors reported in the WMI-Activity trace log.

    Much Googling has produced no solution for this particuar WMI memory leak scenario. I'd appreciate any facts or opinions on how to mitigate or prevent this dilemma. Here's my code:

    using System;  
    using System.Collections.Generic;  
    using System.ComponentModel;  
    using System.Data;  
    using System.Diagnostics;  
    using System.Drawing;  
    using System.Management;  
    using System.Text;  
    using System.Windows.Forms;  
     
    namespace WMIMemTest  
    {  
        public partial class Form1 : Form  
        {  
            ManagementEventWatcher Mew = null;  
     
            public Form1()  
            {  
                InitializeComponent();  
            }  
     
            private void Form1_Load(object sender, EventArgs e)  
            {  
                StartWatcher();  
            }  
     
            private void StartWatcher()  
            {  
                ManagementScope managementScope = null;  
                EventQuery eventQuery = null;  
     
                managementScope = new ManagementScope(@"\\.\root\cimv2", new ConnectionOptions());  
                managementScope.Connect();  
     
                eventQuery = new EventQuery(@"SELECT * FROM __InstanceCreationEvent WHERE TargetInstance ISA 'Win32_NTLogEvent' " +  
                                "and TargetInstance.LogFile = 'Application'");  
     
                Mew = new ManagementEventWatcher(managementScope, eventQuery);  
                Mew.EventArrived += new EventArrivedEventHandler(Dummy);  
                Mew.Start();  
            }  
     
            private void StopWatcher()  
            {  
                if (Mew != null)  
                {  
                    Mew.EventArrived -new EventArrivedEventHandler(Dummy);  
                    Mew.Stop();  
                    Mew.Dispose();  
                }  
            }  
     
            private void Dummy(object sender, EventArrivedEventArgs e)  
            {  
                Debug.WriteLine("EVENT!");  
            }  
     
            private void Form1_FormClosing(object sender, FormClosingEventArgs e)  
            {  
                StopWatcher();  
            }  
     
        }  
    Friday, August 22, 2008 11:03 PM

Answers

  • This is a technical limitation in the ManagementBaseObject class.  You'll need to Dispose it yourself, the framework can't do it for you:

    private void Dummy(object sender, EventArrivedEventArgs e) {
      e.NewEvent.Dispose();
      Debug.WriteLine("EVENT!");
    }

    Normally, the garbage collector will take care of it.  But you are not allocating anything in your test program so the collector never runs and the unmanaged memory consumed by the WMI COM objects grows without bounds.


    Hans Passant.
    Sunday, August 24, 2008 5:48 PM
    Moderator

All replies

  • This is a technical limitation in the ManagementBaseObject class.  You'll need to Dispose it yourself, the framework can't do it for you:

    private void Dummy(object sender, EventArrivedEventArgs e) {
      e.NewEvent.Dispose();
      Debug.WriteLine("EVENT!");
    }

    Normally, the garbage collector will take care of it.  But you are not allocating anything in your test program so the collector never runs and the unmanaged memory consumed by the WMI COM objects grows without bounds.


    Hans Passant.
    Sunday, August 24, 2008 5:48 PM
    Moderator
  • Yes, thank you--you are quite correct. An explicit call to Dispose() is needed to prevent the memory leak.

    Now I need an opinion as to why referencing the ManagementBaseObject property "TargetInstance" causes a memory leak too. Calling Dispose() on the ManagementBaseObject doesn't fix this memory leak:

            private void Dummy(object sender, EventArrivedEventArgs e)  
            {  
                try  
                {  
                    ManagementBaseObject mbo = e.NewEvent;  
                    if (mbo != null)  
                    {  
                        // The presence of the following statement causes a memory leak in this code  
                        if (mbo.Properties["TargetInstance"] != null)  
                        {  
                        }  
                    }  
                }  
                catch  
                {  
                }  
                finally  
                {  
                    e.NewEvent.Dispose();  
                }  
            }  
     


    • Marked as answer by jdmurray Monday, August 25, 2008 5:00 PM
    • Unmarked as answer by jdmurray Monday, August 25, 2008 5:25 PM
    Monday, August 25, 2008 3:56 PM
  • Same problem.  As soon as you reference the property, you'll create the underlying COM object for it.  Which needs to be disposed.  This ought to fix it:

      ManagementBaseObject process = mbo["TargetInstance"];
      if (process != null) {
        // Use it...
        //...
        process.Dispose();
      }

    FWIW, there would be something really wrong if the TargetInstance property isn't available.  It is better to crash your program than allow silent failure:

      using (ManagementBaseObject process = mbo["TargetInstance"]) {
        // Use it, no need to call Dispose now..
        //...
      }

    Hans Passant.
    Monday, August 25, 2008 4:17 PM
    Moderator
  • Thanks for the suggestion, but calling Dispose() on either the TargetInstance object or the ManagementBaseObject object itself doesn't prevent the memory leak. Even with the using statement and multiple, redundant calls to Dispose(), the memory leak is still present. There must be other COM objects instantiated that Dispose() isn't affecting:

    private void Dummy(object sender, EventArrivedEventArgs e)  
            {  
                try  
                {  
                    ManagementBaseObject mbo = e.NewEvent;  
                    if (mbo != null)  
                    {  
                        // The presence of the following statement causes a memory leak in this code  
                        using (ManagementBaseObject process = mbo["TargetInstance"] as ManagementBaseObject)  
                        {  
                            process.Dispose();  
                        }  
                        mbo.Dispose();  
                    }  
                }  
                catch  
                {  
                }  
                finally  
                {  
                    e.NewEvent.Dispose();  
                }  
            } 
    Monday, August 25, 2008 5:00 PM
  • Hard to guess what's leaking.  If you execute no other code than this, you'll perhaps need to call GC.Collect() in a 10 second timer. 

    Why did you mark your post as the answer?  It doesn't look like an answer to me.

    Hans Passant.
    Monday, August 25, 2008 5:16 PM
    Moderator
  • It looks like there's nothing else to Dispose() or free in the .NET interface to the WMI. Placing an explicit call to GC.Collect() immediately after the call to e.NewEvent.Dispose() seems to keep the memory leak somewhat in check, but it may be only delaying the inevitable problem of extreme resource consumption. Only extended testing will tell.

    Thanks for your help. If anyone else has any ideas about this problem, please post them in this thread.
    Monday, August 25, 2008 5:35 PM
  • Use the following method of flush memory after some period to release memmory.

    [DllImportAttribute("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize", ExactSpelling = true, CharSet =
    CharSet.Ansi, SetLastError = true)]
            private static extern int SetProcessWorkingSetSize(IntPtr process, int minimumWorkingSetSize, int
            maximumWorkingSetSize);

            public static void FlushMemory()
            {
                GC.Collect();
                GC.WaitForPendingFinalizers();
                if (Environment.OSVersion.Platform == PlatformID.Win32NT)
                {
                    SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1);
                }
            }

    Contact: imransaeed@my.web.pk

    • Proposed as answer by alexosipov Thursday, August 04, 2011 6:15 PM
    Friday, May 07, 2010 6:17 AM