Backend Development 29 min read

Using Object Pools in .NET (Core) to Reduce GC Overhead and Improve Performance

This article explains how .NET's Microsoft.Extensions.ObjectPool framework can be used to reuse objects, customize pooling policies, integrate with dependency injection, and extend pooling to collections, StringBuilder, arrays and memory buffers, thereby minimizing garbage‑collection pressure and boosting application throughput.

Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Tongcheng Travel Technology Center
Using Object Pools in .NET (Core) to Reduce GC Overhead and Improve Performance

.NET’s automatic garbage collection relieves developers from manual memory management, but the cost of allocating and collecting short‑lived objects can still affect performance; object pools provide a way to reuse instances and avoid unnecessary GC cycles.

The core of the pool is the ObjectPool<T> abstract class, which exposes Get() to borrow an object and Return(T obj) to release it back to the pool. The framework is delivered via the Microsoft.Extensions.ObjectPool NuGet package.

Example of the abstract pool definition and a simple service class:

public abstract class ObjectPool<T> where T: class
{
    public abstract T Get();
    public abstract void Return(T obj);
}
public class FoobarService
{
    internal static int _latestId;
    public int Id { get; }
    public FoobarService() => Id = Interlocked.Increment(ref _latestId);
}

A console program can create a pool with ObjectPool.Create<FoobarService>() and reuse the service objects:

var objectPool = ObjectPool.Create<FoobarService>();
while (true)
{
    var service = objectPool.Get();
    Console.Write($"{service.Id}; ");
    await Task.Delay(1000);
    objectPool.Return(service);
}

When using ASP.NET Core’s DI container, the pool is obtained from an ObjectPoolProvider registered in the IServiceCollection :

var objectPool = new ServiceCollection()
    .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>()
    .BuildServiceProvider()
    .GetRequiredService<ObjectPoolProvider>()
    .Create<FoobarService>();

Custom policies are defined by implementing IPooledObjectPolicy<T> (or inheriting from PooledObjectPolicy<T> ). The example FoobarPolicy creates objects via a static factory and always returns true so the object can be reused:

public class FoobarPolicy : IPooledObjectPolicy<FoobarService>
{
    public FoobarService Create() => FoobarService.Create();
    public bool Return(FoobarService obj) => true;
}

The default implementation DefaultObjectPool<T> stores one object in a field _firstItem and the rest in an array of ObjectWrapper structs, using atomic operations for high‑performance Get/Return. It also detects whether the supplied policy is a DefaultPooledObjectPolicy<T> to skip unnecessary calls.

public class DefaultObjectPool<T> : ObjectPool<T> where T : class
{
    private protected T _firstItem;
    private protected readonly ObjectWrapper[] _items;
    private protected readonly IPooledObjectPolicy<T> _policy;
    private protected readonly bool _isDefaultPolicy;
    // Get and Return implementations use Interlocked.CompareExchange for lock‑free access
}

If the pooled type implements IDisposable , the provider creates a DisposableObjectPool<T> that disposes objects that cannot be returned because the pool is full:

internal sealed class DisposableObjectPool<T> : DefaultObjectPool<T>, IDisposable where T : class
{
    private volatile bool _isDisposed;
    public override void Return(T obj)
    {
        if (_isDisposed || !ReturnCore(obj))
            DisposeItem(obj);
    }
    // DisposeItem calls Dispose on the object if it implements IDisposable
}

Beyond simple services, the article shows how to pool collections ( List<Foobar> ), StringBuilder , arrays via ArrayPool<T> , and memory buffers via MemoryPool<T> . Custom policies control when an object can be returned (e.g., limiting list capacity or StringBuilder size) and ensure cleared state before reuse.

Overall, the .NET object‑pooling infrastructure offers a lightweight, extensible mechanism to reduce allocation churn, control memory usage, and improve throughput for high‑performance backend applications.

performanceDependencyInjectionMemoryManagementdotnetcsharpobjectpool
Tongcheng Travel Technology Center
Written by

Tongcheng Travel Technology Center

Pursue excellence, start again with Tongcheng! More technical insights to help you along your journey and make development enjoyable.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.