How to monitor and respond to memory use

A colleague asked me how to run code in response to low memory condition.

Apparently, data is buffered and can be flushed to disk or a server when memory gets low.

So I showed him the code below.

Start Visual Studio

File->New->Project->C# WPF application

Paste in the code below in the MainWindow.Xaml.cs file

Then hit F5 to run it.

The sample runs some code in a background thread that:

1. Gets the performance counters for the current process. There may be a delay in getting the counters so it checks for Null

2. Gets the values of the counters

3. Updates the textbox with the values

4. Allocates a large string and adds it to a list of strings (consuming large amounts of memory)

5. Sleeps for 1 second

6. Repeats

Eventually, the program runs out of memory

See also

Use Perfmon to analyze your managed memory

<code sample>

 using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1
{
  public partial class MainWindow : Window
  {
    public MainWindow()
    {
      InitializeComponent();
      this.Loaded += (ol, el) =>
      {
        try
        {
          var sp = new StackPanel() { Orientation = Orientation.Vertical };
          this.Content = sp;
          var txtbox = new TextBox() { Text = "hi" };
          var btn = new Button() { Content = "Hit me" };
          sp.Children.Add(btn);
          sp.Children.Add(txtbox);
          bool fDone = false;
          btn.Click += (ob, eb) =>
          {
            fDone = true;
          };
          ThreadPool.QueueUserWorkItem((p) =>
          {
            var start = DateTime.Now;
            var pid = Process.GetCurrentProcess().Id;

            PerformanceCounter perfCounterGCBytes = null;
            PerformanceCounter perfCounterPrivateBytes = null;
            PerformanceCounter perfCounterVirtualBytes = null;
            var listOfBigStuff = new List<string>();

            while (!fDone)
            {
              //update the text box from a different 
              //thread by telling it's dispatcher to do it
              txtbox.Dispatcher.Invoke(() =>
              {
                try
                {
                  if (perfCounterGCBytes == null)
                  {
                    perfCounterGCBytes = GetPerfCounter(
                      ".NET CLR Memory",
                      "# Bytes in all Heaps",
                      "Process ID",
                      pid);
                    perfCounterPrivateBytes = GetPerfCounter(
                      "Process",
                      "Private Bytes",
                      "ID Process",
                      pid);
                    perfCounterVirtualBytes = GetPerfCounter(
                      "Process",
                      "Virtual Bytes",
                      "ID Process",
                      pid);
                  }
                  if (perfCounterGCBytes != null)
                  {
                    var tspan = DateTime.Now - start;
                    var gcBytes = (int)perfCounterGCBytes.NextValue();
                    var privBytes = (int)perfCounterPrivateBytes.NextValue();
                    var virtBytes = (int)perfCounterVirtualBytes.NextValue();

                    txtbox.Text = string.Format(
                        "{0,13:n0} GC={1,-13:n0} priv={2,-13:n0} virt={3,-13:n0} #={4:n0}",
                        tspan.TotalMilliseconds,
                        (int)perfCounterGCBytes.NextValue(),
                        (int)perfCounterPrivateBytes.NextValue(),
                        (int)perfCounterVirtualBytes.NextValue(),
                        listOfBigStuff.Count
                        );
                    // do something to use lots of mem
                    for (int i = 0; i < 10000; i++)
                    {
                      listOfBigStuff.Add(new string('a', 10000));
                    }
                    if (gcBytes> 100000)
                    {
                      // threshold: free some mem

                    }
                  }
                }
                catch (Exception ex)
                {
                  txtbox.Text = ex.ToString();
                  // free some mem
                  listOfBigStuff.Clear();
                }

              });
              Thread.Sleep(1000);
            }
          });



        }
        catch (Exception ex)
        {
          this.Content = ex.ToString();
        }
      };
    }
    public PerformanceCounter GetPerfCounter(string perfcountCat, string perfcountName, string pidstr, int vspid)
    {
      PerformanceCounter pc = null;
      var cat = new PerformanceCounterCategory(perfcountCat);

      foreach (var inst in cat.GetInstanceNames()) // exception if you're not admin or "Performance Monitor Users" group (must re-login)
      {
        //    LogString("Perf count cat = {0} Name = {1}", perfcountCat, inst);

        using (var cntr = new PerformanceCounter(cat.CategoryName, pidstr, inst, true))
        {
          try
          {
            var val = (int)cntr.NextValue();
            if (val == vspid)
            {
              pc = new PerformanceCounter(perfcountCat, perfcountName, inst);
              break;
            }

          }
          catch (Exception)
          {
            // System.InvalidOperationException: Instance 'IntelliTrace' does not exist in the specified Category.
            //                        LogString("Exception {0}", ex.ToString());
          }
        }

      }
      return pc;
    }
  }
}

</code sample>