Managed Memory Model in the .NET Framework

  • 2/18/2009

Pinning

Unmanaged code expects normal pointers, which are assigned a fixed address. For example, a pointer parameter in a native function call is a fixed pointer. A reference in managed code is an abstraction of a moveable pointer. When calling a native function via interoperability, you must be careful about passing references as parameters where pointers are expected. Because the reference is movable, the native call may behave incorrectly or even crash the application. A reference can be fixed in memory, which is called pinning. The referenced object is then considered a pinned object.

Pinned pointers can interfere with normal garbage collection. The Garbage Collector cannot move the memory associated with the pinned objects on the managed heap. Therefore, the generation with the pinned object cannot be fully compacted into contiguous memory. For this reason, pinning is the exception where a generation can possibly become fragmented. Objects that would otherwise fit comfortably in the combined free space do not because of fragmentation. This translates into potentially more garbage collection, which is expensive and harms the performance of the application. Keep pinning to a minimum to avoid this behavior. If possible, pin objects for a short duration—ideally within a garbage collection cycle. This avoids most of the problems in garbage collection related to pinning.

If possible, pin older objects and not younger objects. Older objects are objects that have been promoted to Generation 2. Generation 2 is collected less frequently. Therefore, the Garbage Collector is less likely to have to work around pinned objects. Objects on Generation 0 and 1 are more volatile and move frequently. Pinning objects in these generations creates considerable more work for the Garbage Collector. If an application pins objects regularly, particularly small or young objects, create a pool of pinned objects. Fragmentation is limited because the pinned objects are in contiguous memory and not scattered about the managed heap. This will allow the Garbage Collector to compact storage into contiguous free space more effectively. Performance of the Garbage Collector and application will improve.

There are three ways to pin an object:

  • During interoperability, pinning sometimes occurs automatically. For example, passing strings from managed code into a native API as a method parameter. The managed reference for the string is automatically pinned.

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern int MessageBox(IntPtr hWnd,
        [MarshalAs(UnmanagedType.LPTStr)] string text,
        [MarshalAs(UnmanagedType.LPTStr)] string caption, int options);
    
    static void Main(string[] args) {
        string message = "Hello, world!";
        string caption = "Solid Code";
    
        // pinned
        MessageBox(IntPtr.Zero, message, caption, 0);
    }
  • The fixed statement is used to obtain a native pointer to a reference. In the fixed block, the reference is not moveable and the related pointer can be used.

    public class TwoIntegers {
        public int first = 10;
        public int second = 15;
    }
    
    unsafe static void Main(string[] args) {
        TwoIntegers obj = new TwoIntegers();
    
        // pinned
        fixed(int *pointer=&obj.first) {
            Console.WriteLine("First ={0}", *pointer);
            Console.WriteLine("Second={0}", *(pointer+1));
        }
    }
  • You can also pin objects using the GCHandle type, which is part of the System.Runtime. InteropServices namespace. GCHandle holds a reference to a managed type that can be used in unmanaged code or an unsafe block. As the method name implies, GCHandle. AddrOfPinnedObject returns the address of the pinned object.

    static int[] integers = new int[] { 10, 15 };
    unsafe static void Main(string[] args)
    {
        GCHandle handle = GCHandle.Alloc(integers, GCHandleType.Pinned);
        IntPtr ptrRef= handle.AddrOfPinnedObject();
        int *pointer=(int*)ptrRef.ToPointer();
        Console.WriteLine("First = {0}", *pointer);
        Console.WriteLine("Second = {0}", *(pointer+1));
    }