CEF/Chromium Memory Usage in Long-Running Applications
IronPDF renders with Chromium, so process memory can stay high for a short period after PDF generation even when documents are disposed and GC.Collect() runs. Two sources are at play: Chromium's unmanaged native allocator and IronPDF's browser tab pooling. In most cases this is expected, and it does not automatically mean there is a memory leak.
This applies to IronPDF and IronPdfEngine on Windows and Linux, across Docker, Kubernetes, VM, and server-hosted environments, and to .NET, Java, and any application using Chromium-based rendering.
Why Memory Stays Elevated
Two distinct mechanisms keep memory up after a render finishes.
Chromium native allocator retention: Chromium uses a large amount of unmanaged memory that lives outside the .NET garbage collector. When a render completes, Chromium often keeps that native memory reserved for reuse rather than returning it to the OS immediately. So your PdfDocument objects can be disposed correctly, the runtime can collect managed objects, and the overall process or container memory can still read high. This is normal for Chromium-based engines.
BrowserPool tab reuse: IronPDF can keep idle browser tabs alive after a render so the next one starts faster. Those tabs hold their renderer subprocesses and DOM state for a short time, creating a visible memory baseline even when nothing is rendering. With pooling on, you will typically see memory rise during rendering, hold at a baseline after it completes, then drop in steps as idle tabs time out and are reaped.
Why GC.Collect() Does Not Help
GC.Collect() only reclaims managed .NET memory. It does not force Chromium to hand native memory back to the OS, and it does not tear down warm BrowserPool tabs that are intentionally kept alive for reuse. Even with correct disposal and a forced collection, the figures shown by Task Manager, Docker, Kubernetes, or tools like New Relic may not fall right away.
The effect is most visible in long-running applications, shared service environments, and Docker or Kubernetes deployments. For Java integrations using IronPdfEngine, the pressure usually shows up in the engine process or container rather than the JVM heap.
BrowserPool Defaults
IronPDF keeps a small number of tabs warm between renders to improve performance. The defaults are:
BrowserPool.Enabled = trueBrowserPool.MaxIdleTabs = min(max(ProcessorCount / 2, 1), 4)BrowserPool.IdleTimeoutSeconds = 30
Because of this, even with no active renders the process may keep 1 to 4 idle tabs alive briefly, each holding native memory and subprocess resources until the idle timeout expires. Depending on the rendered content and environment, an idle tab can retain a noticeable amount, roughly 50 to 100 MB in some workloads. That elevated baseline can persist for about 30 seconds after the last render before dropping in steps.
Solution
If your environment is memory-constrained, BrowserPool settings are the first thing to tune. The defaults look like this:
renderer.RenderingOptions.BrowserPool.Enabled = true; // default
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 2; // default is CPU/2, clamped to 1-4
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 30; // default
renderer.RenderingOptions.BrowserPool.Enabled = true; // default
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 2; // default is CPU/2, clamped to 1-4
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 30; // default
net
Option 1: Disable Tab Pooling Completely
Reach for this when you want the most deterministic cleanup after each render.
renderer.RenderingOptions.BrowserPool.Enabled = false;
renderer.RenderingOptions.BrowserPool.Enabled = false;
renderer.RenderingOptions.BrowserPool.Enabled = False
Option 2: Keep BrowserPool Enabled Without Retaining Idle Tabs
Leaves the feature on but avoids holding warm tabs between renders, giving deterministic per-render cleanup.
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0;
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0;
net
Option 3: Reduce the Idle Timeout
A middle ground: keep some reuse benefit, but let memory fall sooner after burst traffic.
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10;
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10;
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10
Choosing between them:
Enabled = false: memory stability matters more than warm-start performance.MaxIdleTabs = 0: you want deterministic per-render cleanup without fully disabling the feature.- Lower
IdleTimeoutSeconds: your workload arrives in bursts and you want memory reclaimed sooner after each one.
Strategies for Long-Running Applications
If your application must hold memory consistently low, work through these in order, starting with BrowserPool tuning before reaching for process recycling.
1. Limit Concurrent Rendering
Several Chromium render jobs running at once raise native memory pressure sharply. If you render in parallel, cap how many run concurrently with a semaphore:
private static readonly SemaphoreSlim RenderSemaphore = new(2);
public async Task<T> RunRenderAsync<T>(Func<T> renderWork)
{
await RenderSemaphore.WaitAsync();
try
{
return renderWork();
}
finally
{
RenderSemaphore.Release();
}
}
private static readonly SemaphoreSlim RenderSemaphore = new(2);
public async Task<T> RunRenderAsync<T>(Func<T> renderWork)
{
await RenderSemaphore.WaitAsync();
try
{
return renderWork();
}
finally
{
RenderSemaphore.Release();
}
}
Imports System.Threading
Private Shared ReadOnly RenderSemaphore As New SemaphoreSlim(2)
Public Async Function RunRenderAsync(Of T)(renderWork As Func(Of T)) As Task(Of T)
Await RenderSemaphore.WaitAsync()
Try
Return renderWork()
Finally
RenderSemaphore.Release()
End Try
End Function
Wrap your IronPDF render call inside the throttled section so only a fixed number of jobs run at a time.
2. Tune or Disable BrowserPool
For many memory-sensitive deployments this is the most effective first step. Disable BrowserPool completely, set MaxIdleTabs = 0, or lower IdleTimeoutSeconds for bursty workloads. Any of these can shrink the persistent post-render baseline without a full restart strategy.
3. Isolate Rendering in a Separate Worker
Moving PDF rendering out of the main application is a strong pattern for production systems. The main app stays stable, the rendering worker can be restarted on its own, and native Chromium memory is fully released when that worker exits. Before relying on recycling, confirm whether disabling BrowserPool or setting MaxIdleTabs = 0 already gives you predictable per-render cleanup. The isolation approach pays off most in Docker or Kubernetes environments, background job systems, and API platforms that need stricter memory isolation.
4. Recycle the Worker Only When Necessary
Where there is a hard memory ceiling and BrowserPool tuning still falls short, recycle the rendering process or worker container after a set number of jobs. That might mean restarting a dedicated worker after a fixed batch size, rotating engine containers in Kubernetes, or isolating rendering into a service that can restart independently. Treat this as a later step, not your first move.
When It Might Be a Leak
Elevated memory after rendering does not automatically mean a leak. Chromium-based rendering has two kinds of reuse to understand: allocator-level reuse, where Chromium reserves native memory pages internally (normal, not directly controllable), and tab-level reuse, where BrowserPool keeps full tabs alive between renders (configurable, and often the bigger contributor to what you see in monitoring tools).
The pattern is usually expected when memory rises during rendering, stabilizes instead of growing without limit, stays elevated briefly afterward, and later drops as idle tabs time out or remains available for the next render.
Investigate further when:
- memory keeps increasing without stabilizing under a similar workload
- memory keeps growing even after reducing concurrency
- memory keeps growing even after disabling BrowserPool or setting
MaxIdleTabs = 0 - a minimal reproduction shows the same pattern over time with controlled input
When to Contact Support
Reach out to technical support if you can reproduce all of the following:
- memory continues growing without stabilizing under a controlled workload
- the issue persists after limiting concurrency
- the issue persists after disabling BrowserPool or setting
MaxIdleTabs = 0 - you can reproduce it with a minimal sample project
When opening a ticket, include your IronPDF version, OS and hosting environment, Docker or Kubernetes details if applicable, programming language, render frequency and concurrency level, memory graphs or monitoring screenshots, and a minimal reproducible sample.

