Tuesday, June 3, 2014

Garbage Collector and Memory Fragmentation

Sometimes I've memory problems... Sorry, this is another story.
In .net sometimes we can have memory allocation problems.

If you allocate too many objects you can fragment heap memory.
Though you have sufficient free memory the system could not be able to find a block of memory to use for allocation request.
In this case you obtain "System.OutOfMemoryException" though there is still available memory.

This scenario can occur when you have WPF application or when you need to load a large object when memory is fragmented due to loading and unloading objects.

If you have those problems the class below is for you.
In your application you can call MemoryTestApp.MemoryUtils.CompactMemory();
With this method Garbage collector is called (just in case) and are called Windows API to "compact a specified heap by coalescing adjacent free blocks of memory and decommitting large free blocks of memory"
The main API called are:

  • SetProcessWorkingSetSize
  • HeapCompact

With first API we ensure that the working size in memory allocation is set to the minimum.
With second API we compact all allocated block of memory.

First we say to the system that we want the minimum page size when one object is allocated to the heap, second we compact all the allocated memory's pages.


using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace MemoryTestApp
{
    /// <summary>
    /// Utilities to free anche compact memory
    /// </summary>
    public static class MemoryUtils
    {
        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetProcessWorkingSetSize(
            IntPtr process,
            UIntPtr minimumWorkingSetSize,
            UIntPtr maximumWorkingSetSize);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr GetProcessHeap();

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool HeapLock(IntPtr heap);

        [DllImport("kernel32.dll")]
        private static extern uint HeapCompact(IntPtr heap, uint flags);

        [DllImport("kernel32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool HeapUnlock(IntPtr heap);

        private static void SetProcessWorkingSetSizeToMin()
        {
            SetProcessWorkingSetSize(
                Process.GetCurrentProcess().Handle,
                (UIntPtr)0xFFFFFFFF,
                (UIntPtr)0xFFFFFFFF);
        }

        /// <summary>
        /// Call Garbage collector, wait for it and Compact heap memory
        /// </summary>
        public static void CompactMemory()
        {
            CompactMemory(true, true);
        }
        /// <summary>
        /// Compact heap memory
        /// </summary>
        /// <param name="gcCollect">If true call GC.Collect</param>
        /// <param name="waitCollect">If true wait for GC.Collect</param>
        public static void CompactMemory(bool gcCollect, bool waitCollect)
        {
            if (gcCollect && waitCollect)
            {
                GC.Collect(GC.MaxGeneration);
                GC.WaitForPendingFinalizers();
            }
            else if (gcCollect && !waitCollect)
            {
                GC.Collect(GC.MaxGeneration);
            }

            SetProcessWorkingSetSizeToMin();
            HeapCompact();
        }

        private static void HeapCompact()
        {
            IntPtr heap = GetProcessHeap();

            if (HeapLock(heap))
            {
                try
                {
                    if (HeapCompact(heap, 0) == 0)
                    {
                        //error ignored
                    }
                }
                finally
                {
                    HeapUnlock(heap);
                }
            }
        }

    }
}

2 comments:

  1. Very very good tips Andrea! But I think we must always balance effort to write prognostic feature from scratch and the benefit in a non critical application. But in critical environments it could be necessary and also there are suites to accomplish this like nagio or many others

    ReplyDelete
    Replies
    1. Hi Marco, thanks for the comment. I think it was for "prognostic feature" page instead of this post.

      Delete