MemoryPool是生产级内存管理组件,用于避免GC压力和内存泄漏;需严格遵循租用-归还契约,正确使用Slice和using确保安全。

MemoryPool<t></t> 不是用来“精通”的工具,而是用对了能省 GC、用错了会泄漏的生产级内存管理组件。它不是语法糖,也不该出现在 demo 里就完事——你得知道什么时候必须用、怎么租才安全、归还漏了会发生什么。
为什么不能直接 new byte[4096]?
频繁分配小数组(比如网络收包、序列化缓冲)会导致:托管堆碎片 + Gen0 GC 频繁触发 + Array 对象反复创建销毁。尤其在高吞吐服务中,new byte[4096] 每秒几百次,GC 压力肉眼可见。而 MemoryPool<byte>.Shared.Rent(4096)</byte> 复用的是池中已分配好的数组块,跳过分配路径,也绕开 GC 跟踪。
MemoryPool<t>.Rent(int)</t> 返回的内存大小不等于请求值
池内部按 2 的幂预分配(如 256、512、1024、2048、4096、8192…),Rent(3000) 实际返回的 Memory<byte></byte> 长度很可能是 4096。你必须用 memory.Length 或显式截取,而不是硬写 buffer.AsSpan(0, 3000) ——否则可能越界读写。
- 错误写法:
var memory = owner.Memory; var span = memory.Span; span[3000] = 1;(越界风险) - 正确写法:
var span = memory.Slice(0, Math.Min(memory.Length, 3000)).Span; - 更推荐:
var span = memory.Span[..Math.Min(memory.Length, 3000)];
忘记 Dispose() 就等于内存泄漏
IMemoryOwner<t></t> 是 IDisposable,但它的 Dispose() 不是释放托管资源,而是把内存块**归还给池**。漏掉这一步,池里可用块越来越少,后续 Rent() 只能不断 new 新数组,最终退化为纯堆分配。
- 必须用
using语句确保归还:using (var owner = pool.Rent(4096)) { ... } - 不能在异步方法中跨 await 持有
owner并手动Dispose()—— await 后线程可能切换,owner可能被提前回收或重复释放 - 如果必须跨 await 使用内存内容,请
Slice()出ReadOnlyMemory<t></t>或拷贝到局部变量,别传owner本身
ArrayPool<t></t> 和 MemoryPool<t></t> 到底选哪个?
二者底层都基于 ArrayPool<t></t>,但抽象层级和使用契约不同:
-
ArrayPool<t>.Rent()</t>返回T[],你负责调用Return();不带生命周期管理,容易忘还 -
MemoryPool<t>.Rent()</t>返回IMemoryOwner<t></t>,天然支持using,归还由 Dispose 保障;且返回的是Memory<t></t>,可无缝对接Span、IBufferWriter、SequenceReader等现代 API - 如果你只处理字节数组且逻辑极简(如同步日志缓冲),
ArrayPool更轻量;但只要涉及异步、流式处理、或需要与System.IO.Pipelines集成,MemoryPool是唯一合理选择
Rent(),而是理解「租用」这个隐喻:你没拿到所有权,只是临时持有;归还不是可选项,是契约义务;池不会提醒你漏还,它只会默默变慢。


















