arraypool是.net提供的数组对象池,用于复用t[]实例以降低gc压力,适用于生命周期短、大小相对固定且频繁申请释放的临时数组场景。

ArrayPool 是什么,什么时候该用它ArrayPool<t></t> 是 .NET 提供的数组对象池,核心目标是复用 T[] 实例,避免频繁分配和释放导致 GC 压力升高。它不是万能的:只适合生命周期短、大小相对固定、且频繁申请/释放的临时数组场景,比如序列化缓冲区、网络包解析、图像帧处理等。
- 长期持有数组(比如缓存数秒以上)会阻塞池中资源,反而降低复用率
- 数组大小差异过大(如一会儿申请 1024,一会儿申请 65536)会导致大量“溢出数组”(external arrays),这些不进池,起不到减压效果
- 小于 1024 字节的数组(如
new byte[128])在 .NET Core 3.0+ 默认走栈上分配(stackalloc)或 Gen0 快速回收,用池可能得不偿失
怎么正确申请和归还数组
申请必须用 ArrayPool<t>.Shared.Rent(int minimumLength)</t>,归还必须调用 pool.Return(array, clearArray: false) —— 这两步缺一不可,且顺序不能颠倒。
-
Rent() 返回的数组长度 ≥ 请求的 minimumLength,但不保证等于;务必检查 array.Length,不要硬编码下标边界
- 归还时
clearArray: false 是默认且推荐值:清零会带来额外开销,业务层应在使用前明确初始化相关区域(比如 Array.Clear(array, 0, usedLength))
- 忘记
Return() 会导致池“泄漏”,池中可用数组越来越少,后续 Rent() 更多返回新分配数组,GC 压力不降反升
- 不要对同一数组多次
Return(),会触发 ArgumentException:“The array has already been returned to the pool.”
自定义 ArrayPool 有哪些关键配置
ArrayPool<t>.Create()</t> 允许定制,但多数场景用 Shared 就够了。真要自定义,注意三个参数:
-
maxArrayLength:池中单个数组最大长度(默认 1024 × 1024)。超过此值的请求直接 new,不入池
-
maxArraysPerBucket:每个尺寸桶最多存几份(默认 50)。设太小易频繁新建;设太大浪费内存
- 桶尺寸策略是 2 的幂次分组(如请求 1000 → 归入 1024 桶),所以实际复用率取决于你的请求长度分布是否集中
示例:若业务总用 4096 和 8192 的 byte[],可建专用池:
var myPool = ArrayPool<byte>.Create(maxArrayLength: 8192, maxArraysPerBucket: 100);
避免和 Shared 混用,否则尺寸错位导致归还不生效。
常见误用与 GC 反效果陷阱
最典型的问题不是“没用”,而是“用错了却以为有用”。
- 在
using 中包装 Rent() 结果?不行。ArrayPool 返回的是数组引用,没有实现 IDisposable,using 无意义,还可能掩盖未 Return() 的 bug
- 把
Rent() 放在循环里,但 Return() 写在循环外?会导致一次只归还最后一个数组,其余全部泄漏
- 多线程共用一个租来的数组且无同步?
ArrayPool 本身线程安全,但数组内容不是,读写冲突由你负责
- 用完
Return() 后继续访问该数组?行为未定义,可能读到旧数据,也可能触发内存访问异常(尤其开启 unsafe 或 Span 越界检查时)
复用收益真实存在,但前提是:租得准、用得短、还得及时。漏掉任一环,GC 计数器不会说谎。
ArrayPool<t></t> 是 .NET 提供的数组对象池,核心目标是复用 T[] 实例,避免频繁分配和释放导致 GC 压力升高。它不是万能的:只适合生命周期短、大小相对固定、且频繁申请/释放的临时数组场景,比如序列化缓冲区、网络包解析、图像帧处理等。
- 长期持有数组(比如缓存数秒以上)会阻塞池中资源,反而降低复用率
- 数组大小差异过大(如一会儿申请 1024,一会儿申请 65536)会导致大量“溢出数组”(external arrays),这些不进池,起不到减压效果
- 小于 1024 字节的数组(如
new byte[128])在 .NET Core 3.0+ 默认走栈上分配(stackalloc)或 Gen0 快速回收,用池可能得不偿失
怎么正确申请和归还数组
申请必须用 ArrayPool<t>.Shared.Rent(int minimumLength)</t>,归还必须调用 pool.Return(array, clearArray: false) —— 这两步缺一不可,且顺序不能颠倒。
-
Rent()返回的数组长度 ≥ 请求的minimumLength,但不保证等于;务必检查array.Length,不要硬编码下标边界 - 归还时
clearArray: false是默认且推荐值:清零会带来额外开销,业务层应在使用前明确初始化相关区域(比如Array.Clear(array, 0, usedLength)) - 忘记
Return()会导致池“泄漏”,池中可用数组越来越少,后续Rent()更多返回新分配数组,GC 压力不降反升 - 不要对同一数组多次
Return(),会触发ArgumentException:“The array has already been returned to the pool.”
自定义 ArrayPool 有哪些关键配置
ArrayPool<t>.Create()</t> 允许定制,但多数场景用 Shared 就够了。真要自定义,注意三个参数:
-
maxArrayLength:池中单个数组最大长度(默认 1024 × 1024)。超过此值的请求直接 new,不入池 -
maxArraysPerBucket:每个尺寸桶最多存几份(默认 50)。设太小易频繁新建;设太大浪费内存 - 桶尺寸策略是 2 的幂次分组(如请求 1000 → 归入 1024 桶),所以实际复用率取决于你的请求长度分布是否集中
示例:若业务总用 4096 和 8192 的 byte[],可建专用池:
var myPool = ArrayPool<byte>.Create(maxArrayLength: 8192, maxArraysPerBucket: 100);避免和
Shared 混用,否则尺寸错位导致归还不生效。
常见误用与 GC 反效果陷阱
最典型的问题不是“没用”,而是“用错了却以为有用”。
- 在
using中包装Rent()结果?不行。ArrayPool返回的是数组引用,没有实现IDisposable,using无意义,还可能掩盖未Return()的 bug - 把
Rent()放在循环里,但Return()写在循环外?会导致一次只归还最后一个数组,其余全部泄漏 - 多线程共用一个租来的数组且无同步?
ArrayPool本身线程安全,但数组内容不是,读写冲突由你负责 - 用完
Return()后继续访问该数组?行为未定义,可能读到旧数据,也可能触发内存访问异常(尤其开启unsafe或Span越界检查时)
复用收益真实存在,但前提是:租得准、用得短、还得及时。漏掉任一环,GC 计数器不会说谎。










