Zolt
Zolt

Reputation: 2811

C# code to create a memory leak.

I'd like to write a simple C# program / console application to create a slow and controlled memory leak. I want the program to slowly eat up system memory. I need this for something else that I am testing.

It was suggested to me to use:

Marshal.AllocHGlobal(numbytes)

attached to a timer.

Here is the code I tried to implement:

class Program
{
    static Timer timer = new Timer();

    static void Main(string[] args)
    {
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);

        start_timer();
        Console.Read();
    }

    static void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        IntPtr hglobal = Marshal.AllocHGlobal(100);
        Marshal.FreeHGlobal(hglobal);

        Process proc = Process.GetCurrentProcess();
        Console.WriteLine(proc.PrivateMemorySize64);
    }

    private static void start_timer()
    {
        timer.Start();
    }
}

The output seems to indicated that memory is still being managed and garbage collection is doing its job. Can someone help me out with this? My goal is to indeed create a memory leak and use up system resources. Someone please suggest what is wrong with my code, or an alternative, better option.

Upvotes: 1

Views: 845

Answers (2)

Frank The Tank
Frank The Tank

Reputation: 451

I needed to create a controlled memory leak for a failure mode test case for an app running in Azure App Services.

This is a drop in MVC class.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Web;
using System.Web.Mvc;
using log4net;

namespace foo.FooApp.MVC.UI.Controllers
{
    [Route("memleak")]
    public class MemLeakController : Controller
    {
        /*
         * This code is a simple drop in solution that can be placed into a MVC controller application and will create a controlled memory leak.
         * It provides three endpoints
         * /InitMem - This is the initial page that provides a link to start the memory leak
         * /RunMem - This page takes two parameters and will create a controlled memory leak.
         * * Parameter 1: MemToAllocate, this is the amount of memory to consume per each iteration.
         * * Parameter 2: MemToConsume, This is the number of allocation iterations.
         * /ClearMem - This will reset (clear) any memory allocations.
         * It was determined using a List of Bytes for memory allocation was a better solution than some of the other techniques, for example: Marshal.AllocHGlobal and static event EventHandler.
         * For measuring memory allocation it was determined that using GC.GetTotalMemory rather proc.PrivateMemorySize64 provided more accurate results.
         * This test will eventually cause an System.OutOfMemoryException. The Test can be reset by clicking on the clear memory link
         * As of now App Services will fail with out of memory after about 3.4 GB of memory allocation.
         */
        private static readonly ILog Logger =
            LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod()?.DeclaringType);

        private const string MemClearHtml = "<ul><li><a href=\"/memleak/ClearMem\")\">"
                                            + "<span>Reset Memory Leak Test</span>"
                                            + "</a></li></ul>";

        private const string MemRunHtml = "<ul><li><a href=\"/memleak/RunMem?MemToConsume=5&MemToAllocate=100\")\">"
                                          + "<span>Run Memory Leak Test</span>"
                                          + "</a></li></ul>";

        private const string TextareaHeader = "<textarea id=\"foo\" rows=\"8\" cols=\"50\">";
        private const string TextareaFooter = "</textarea>";
        private static long _memToAllocate = 1000000 * 100; // 100 MB
        private static int _memToConsume = 5; // * _memToAllocate = 500 MB
        private static long _lastMem;

        // ReSharper disable once CollectionNeverQueried.Local
        private static readonly List<byte[]> ListToConsume = new List<byte[]>();

        /*
         * Initial page to start the memory leak test
         * Endpoint URI: /memleak
         */
        [HttpGet]
        public void Index()
        {
            Logger.Info("Init MEM " + GC.GetTotalMemory(false));
            Response.Output.Write("Start Memory Leak Test {0} <br><br>", MemRunHtml);
            Response.Output.Write("Current Memory Bytes: {0} <br>", GC.GetTotalMemory(false));
            Response.Output.Write("Current Memory MB:    {0} <br><br>",
                GC.GetTotalMemory(false) / (1024 * 1024));
        }

        /*
         * Main page for submitting memory allocation
         * Endpoint URI: /memleak/RunMem?MemToConsume=5&MemToAllocate=100
         */
        public void RunMem()
        {
            try
            {
                if (!string.IsNullOrEmpty(Request.QueryString.ToString()))
                {
                    var nameValueCollection = HttpUtility.ParseQueryString(Request.QueryString.ToString());
                    _memToAllocate = long.Parse(nameValueCollection["MemToAllocate"]) * 1000000; // 1 MB
                    _memToConsume = int.Parse(nameValueCollection["MemToConsume"]);
                }

                Response.Output.Write("Memory Leak Test {0} <br><br>", MemClearHtml);

                Response.Output.Write("Memory Leak Test {0} {1:HH:mm:ss} <br>", "Start:", DateTime.Now);
                Response.Output.Write("Current Memory Bytes: {0} <br>", GC.GetTotalMemory(false));
                Response.Output.Write("Current Memory MB:    {0} <br><br>",
                    GC.GetTotalMemory(false) / (1024 * 1024));
                Logger.Info("Start MEM " + GC.GetTotalMemory(false));

                Response.Output.Write(TextareaHeader);
                for (var i = 0; i < _memToConsume; i++)
                {
                    ListToConsume.Add(new byte[_memToAllocate]);
                    Thread.Sleep(100);
                    var mem = GC.GetTotalMemory(false);
                    if (_lastMem == mem) continue;
                    Response.Output.Write("Current Memory Bytes: {0} \n", mem);
                    Response.Output.Write("Current Memory MB:    {0} \n", mem / (1024 * 1024));
                    Logger.Info("Start MEM " + GC.GetTotalMemory(false));
                    _lastMem = mem;
                }

                Response.Output.Write(TextareaFooter);

                Thread.Sleep(100);
                Response.Output.Write("<br><br>Current Memory Bytes: {0} <br>", GC.GetTotalMemory(false));
                Response.Output.Write("Current Memory MB:    {0} <br>",
                    GC.GetTotalMemory(false) / (1024 * 1024));
                Response.Output.Write("Memory Leak Test {0} {1:HH:mm:ss} <br><br><br>", "End:", DateTime.Now);

                Response.Output.Write("Consume Next {0} {1} ",
                    _memToAllocate * _memToConsume / (1024 * 1024) + " MB",
                    MemRunHtml);
                Logger.Info("Start MEM " + GC.GetTotalMemory(false));
            }
            catch (Exception ex)
            {
                Logger.Error(ex.Message);
                LogError(ex);
            }
        }

        /*
         * Page to reset (clear) the memory allocation
         * Endpoint URI: /memleak/ClearMem
         */
        public void ClearMem()
        {
            Response.Output.Write("Start Memory Overload Test {0} <br><br>", MemRunHtml);
            ListToConsume.Clear();
            GC.GetTotalMemory(true);
            Thread.Sleep(100);
            Response.Output.Write("<br>Current Memory Bytes: {0} <br>", GC.GetTotalMemory(false));
            Response.Output.Write("Current Memory MB:    {0} <br>",
                GC.GetTotalMemory(false) / (1024 * 1024));
            Response.Output.Write("Memory Overload {0} {1:HH:mm:ss} <br>", "End:", DateTime.Now);
            Logger.Info("Stop MEM " + GC.GetTotalMemory(false));
        }

        private void LogError(Exception ex)
        {
            LogManager.GetLogger(typeof(MvcApplication)).Error(ex);
        }
    }
}

Upvotes: 0

nvoigt
nvoigt

Reputation: 77294

For a leak, you need to actually leak memory. Right now, you do not leak any, because you free it:

    IntPtr hglobal = Marshal.AllocHGlobal(100);
    // DO NOT DO THIS IF YOU WANT A LEAK: Marshal.FreeHGlobal(hglobal);

Upvotes: 3

Related Questions