应传非null IPooledObjectPolicy并确保Return()执行到位,否则对象无法复用;DefaultPooledObjectPolicy仅对少数BCL类型自动重置,自定义类需手动实现Clear或IResettable;MaximumRetained超限会丢弃对象,高并发下宜分池而非扩池。

直接用 Microsoft.Extensions.ObjectPool 就行,但别一上来就 new 一堆池子——对象没复用成功,八成是策略没配对、Return() 没执行到位,或者压根没意识到 MaximumRetained 在悄悄丢对象。
为什么 new DefaultObjectPool 会返回 null
因为无参构造或传了 null 策略时,Get() 不创建实例,只返回 default(StringBuilder)(即 null)。这不是 bug,是强制你声明“怎么造、怎么清”。
- 必须传非
null的IPooledObjectPolicy<t></t>,哪怕只是new DefaultPooledObjectPolicy<stringbuilder>()</stringbuilder> -
DefaultPooledObjectPolicy<t></t>只对StringBuilder、MemoryStream等少数 BCL 类型自动调Clear()或重置位置;对自定义类完全无效 - 别信“默认就清空”,检查源码可知:它靠反射找
Clear()或IResettable.Reset(),没这方法就跳过
Return() 被跳过才是复用失败的真凶
现象是“每次 Get() 都拿到新对象”,不是池坏了,而是归还路径断了。
- 在
async void方法里调Return(),方法提前退出,归还不执行 -
try块里出异常,finally没包住Return(),对象永久泄漏 -
Return()前字段已被设为null,导致策略的Return(T obj)内部判空失败并抛异常,池捕获后直接丢弃该实例 - 池设置了
MaximumRetained = 100(DefaultObjectPoolProvider默认值),第 101 个空闲对象会被 GC,不进池
高并发下 Get() 变慢?先看是不是 Create() 或竞争在拖后腿
DefaultObjectPool<t></t> 内部用 ConcurrentStack<t></t>,理论无锁,但真实瓶颈常不在池本身。
- 如果
Create()里有 IO、锁、或耗时初始化(比如Thread.Sleep(5000)),整个线程的Get()都会被卡住 - 多个线程争抢同一个池,在极端高 QPS 下仍可能因 CPU cache line bouncing 抖动
- 解决方案不是换池,而是分池:
AsyncLocal<objectpool>></objectpool>每线程一个池,或按租户 ID 哈希分桶 -
Return()方法里禁止任何阻塞操作——哪怕是简单日志写入,都会拖慢所有归还路径
池大小不是越大越好,内存和缓存效率得一起算
MaximumRetained 是 per-pool 的,每个 Create() 调用独立计数。盲目调大有实际代价:
- 内存常驻更多对象,GC 压力前移,尤其对象含大数组时容易吃光内存
- 池子过大导致 CPU 缓存失效率上升,反而降低访问局部性
- 多线程争抢池锁(虽是
ConcurrentStack,但非零开销) - 高频不可变对象(如每请求新建的
JsonSerializerOptions)不宜池化——new成本极低,池引入的线程安全开销反而更高
最易被忽略的一点:对象池不管理对象生命周期,只管借还。你得确保 Return() 前对象状态可安全复用,且 Return() 一定被执行——漏一次,就少一个可复用实例;多一次,会抛 InvalidOperationException: Object is already in the pool。


















