Frage

I am trying to write a class that wraps a buffer allocated with Marshal.AllocHGlobal. I implemented the IDisposable interface, and added a finalizer that should release the memory when I don't need it anymore (when the object goes out of scope).

When I test the class, the GC does not call the finalizer or the Dispose method of my classes, even though they are out of scope. As a result, I get an OutOfMemoryException.

Why does the GC not call the finalizer, and why does the memory not get freed?

Here is a short example that illustrates the problem. In the sample, there is nothing written to the console (except Unhandled Exception: OutOfMemoryException.)

class Buffer : IDisposable
{
    public IntPtr buf { get; set; }

    public Buffer()
    {
        buf = Marshal.AllocHGlobal(4 * 1024 * 1024);
    }

    ~Buffer()
    {
        Console.WriteLine("Finalizer called");
        Dispose(false);
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose called");
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    internal virtual void Dispose(bool disposing)
    {
        if (buf != IntPtr.Zero)
        {
            Console.WriteLine("Releasing memory");
            Marshal.FreeHGlobal(buf);
            buf = IntPtr.Zero;
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        while(true)
        {
            Buffer b = new Buffer();
            Thread.Sleep(20);
        }
    }
}

EDIT: Here is the .NET performance counters for my test program when it crashes: Performance counters

War es hilfreich?

Lösung

You need to tell the garbage collector that your very small managed objects with a single IntPtr field have a high cost in terms of unmanaged memory. Currently, the garbage collector is blissfully unaware of the fact that each small managed object uses a large amount of unmanaged memory and has no reason to perform any collection.

You can use the GC.AddMemoryPressure when you allocate the unmanaged memory and GC.RemoveMemoryPressure when you free the unmanaged memory.

Andere Tipps

Garbage collection occurs when one of the following conditions is true:

  1. The system has low physical memory.
  2. The memory that is used by allocated objects on the managed heap surpasses an acceptable threshold. This threshold is continuously adjusted as the process runs.
  3. The GC.Collect method is called. In almost all cases, you do not have to call this method, because the garbage collector runs continuously. This method is primarily used for unique situations and testing.

Also the garbage collector tracks memory only on the managed heap, so for this program, the only condition can trigger GC is the first one.

I compiled the program, if the target CPU is x86, it will through out of memory exception when the private bytes of the process reaches about 2G. When I run the program, I noticed the private bytes increase quickly, but the working set increase very slowly, and also the system physical memory usage increase very slowly.

As private bytes and working set, this post explains: Private Bytes refer to the amount of memory that the process executable has asked for - not necessarily the amount it is actually using. They are "private" because they (usually) exclude memory-mapped files (i.e. shared DLLs). But - here's the catch - they don't necessarily exclude memory allocated by those files. There is no way to tell whether a change in private bytes was due to the executable itself, or due to a linked library. Private bytes are also not exclusively physical memory; they can be paged to disk or in the standby page list (i.e. no longer in use, but not paged yet either).

Working Set refers to the total physical memory (RAM) used by the process. However, unlike private bytes, this also includes memory-mapped files and various other resources, so it's an even less accurate measurement than the private bytes. This is the same value that gets reported in Task Manager's "Mem Usage" and has been the source of endless amounts of confusion in recent years. Memory in the Working Set is "physical" in the sense that it can be addressed without a page fault; however, the standby page list is also still physically in memory but not reported in the Working Set, and this is why you might see the "Mem Usage" suddenly drop when you minimize an application.

Marshal.AllocHGlobal just increases the private bytes, but the working set is still small, it doesn't trigger GC either.

Please refer this: Fundamentals of Garbage Collection

IDisposable is declarative, The dispose method is only called when Garbage collection actually happens.

Yoy can force garbage collection to happen , for this You need to call

GC.Collect

http://msdn.microsoft.com/en-us/library/xe0c2357(v=vs.110).aspx

I would also recommend using preformance counters to see you app memory consumtion, and see if GC is already called. see here how to do it http://msdn.microsoft.com/en-us/library/x2tyfybc(v=vs.110).aspx

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top