Marshal.FreeHGlobal内存释放原理及C#内存残留问题咨询
关于C#非托管内存释放后进程内存残留的问题解答
一、为什么释放后进程内存仍有1-2MB增长?
这个现象其实是Windows内存管理机制和CLR托管内存模型共同作用的结果,核心原因有这几点:
- 非托管内存的回收特性:你用
Marshal.AllocHGlobal分配的是非托管内存,调用FreeHGlobal释放后,操作系统并不会立刻把这部分内存归还给系统,而是将其保留在当前进程的内存池中,作为后续非托管内存分配的备用空间——这么做是为了避免频繁向系统申请/释放内存带来的性能开销。 - 托管对象未被GC回收:
List<clsA>里存储的是clsA的托管实例,虽然你调用free()释放了非托管内存,但这些clsA对象本身还留在托管堆中。直到CLR的垃圾回收器(GC)触发回收,才会释放这些托管对象占用的内存。而你的代码里List<clsA>的引用一直存在到Main方法后期,GC可能还没来得及处理这部分内存。 - 内存统计的延迟性:任务管理器显示的内存占用是进程的工作集大小(Working Set),包含当前正在使用的物理内存。操作系统需要时间调整工作集大小,不会在内存释放后立刻更新统计数据。
二、如何释放剩余内存?
如果你确实需要尽可能回收这部分内存,可以尝试以下方法,但要注意这些方法都有适用场景,不要滥用:
- 触发强制GC回收
在释放非托管内存后,先让clsA实例失去引用,再手动触发GC(这只是建议GC执行回收,无法保证立刻完成):foreach (var a in l) { a.free(); } // 清除引用,让GC能识别到可回收对象 l.Clear(); l = null; // 触发GC并等待回收完成 GC.Collect(); GC.WaitForPendingFinalizers(); - 提示操作系统回收物理内存
通过Windows API调整进程工作集大小,让操作系统尽可能回收未使用的物理内存(注意这不会减少虚拟内存占用,只是调整物理内存分配):[System.Runtime.InteropServices.DllImport("kernel32.dll")] private static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max); // 在GC回收后调用 SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); - 采用规范的资源释放模式
让clsA实现IDisposable接口,用using语句自动管理资源,既能保证非托管内存及时释放,也能让GC更快识别需要回收的对象:class clsA : IDisposable { private IntPtr _buffer; private bool _disposed = false; public clsA() { _buffer = Marshal.AllocHGlobal(1024); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (_buffer != IntPtr.Zero) { Marshal.FreeHGlobal(_buffer); _buffer = IntPtr.Zero; } _disposed = true; } ~clsA() { Dispose(false); } } // 使用示例 static void Main(string[] args) { List<clsA> l = new List<clsA>(); for(int i = 0; i < 8192; i++) { l.Add(new clsA()); } Console.WriteLine("Added"); foreach (var a in l) { a.Dispose(); } l.Clear(); l = null; GC.Collect(); GC.WaitForPendingFinalizers(); Console.ReadLine(); }
三、Marshal.FreeHGlobal是如何释放内存的?
Marshal.FreeHGlobal本质是对Windows系统API LocalFree的封装,工作流程如下:
- 它接收的指针必须是通过
Marshal.AllocHGlobal分配的,这个指针指向**Windows全局堆(Global Heap)**中的内存块。 - 调用
LocalFree后,操作系统会把该内存块标记为“可用”,但不会立刻将内存归还给系统,而是保留在进程的全局堆池中,供后续AllocHGlobal调用时直接复用——这是为了提升内存分配的性能。 - 注意:
FreeHGlobal只能释放AllocHGlobal分配的内存,不能用于AllocCoTaskMem的内存(后者需要用Marshal.FreeCoTaskMem,它封装的是CoTaskMemFree,对应COM任务堆的内存管理)。
内容的提问来源于stack exchange,提问作者Harsh Shah
相关产品推荐
相关产品推荐

