Skip to content
English
  • There are no suggestions because the search field is empty.

IronPDF - Understanding CEF/Chromium Memory Usage in Long-Running Applications

IronPDF uses Chromium-based rendering, so memory usage after PDF generation can come from two different sources: Chromium’s unmanaged/native allocator behavior and IronPDF’s browser tab pooling. Because of this, memory may remain high for a short period after rendering completes, even when documents are disposed and GC.Collect() is called. In many cases, this is expected behavior and does not automatically indicate a memory leak.

Applies to

  • IronPDF
  • IronPdfEngine
  • Windows and Linux
  • Docker, Kubernetes, VM, and server-hosted environments
  • .NET, Java, and applications using Chromium-based rendering

Why memory usage may stay high after rendering

IronPDF relies on CEF/Chromium for rendering, and memory usage after rendering can remain elevated for two different reasons.

1. Chromium native allocator retention

Chromium uses a large amount of unmanaged/native memory, which exists outside the .NET garbage collector. After rendering completes, Chromium may keep previously allocated native memory available for reuse instead of immediately returning it to the operating system.

This means:

  • PdfDocument objects may be correctly disposed
  • the .NET runtime may collect managed objects
  • but the overall process or container memory may still stay high

This is normal behavior for Chromium-based rendering engines.

2. BrowserPool tab reuse

In addition to Chromium’s native allocator behavior, IronPDF may keep idle browser tabs alive after rendering so future renders can start faster. These tabs can keep their renderer subprocesses and DOM state alive for a short time, which creates a visible memory baseline even when no render is currently running.

By default, BrowserPool is enabled. This means memory may stay elevated briefly after the last render, then drop in steps as idle tabs are cleaned up.

In practical terms, users monitoring memory may see:

  1. memory rises during rendering
  2. a baseline remains after rendering completes
  3. memory drops later as idle tabs time out and are reaped

This behavior is expected and does not automatically indicate a memory leak.

Why GC.Collect() may not reduce memory usage

Calling GC.Collect() only affects managed .NET memory.

It does not force Chromium to immediately release native memory back to the operating system. It also does not force BrowserPool to immediately tear down warm browser tabs that are intentionally being kept alive for reuse.

So even if your code disposes documents properly and runs garbage collection, the memory shown by Task Manager, Docker, Kubernetes, or monitoring tools such as New Relic may not drop right away.

This is especially important in:

  • long-running applications
  • shared service environments
  • Docker or Kubernetes deployments
  • Java integrations using IronPdfEngine

For Java + IronPdfEngine scenarios, the memory pressure may be most visible in the engine process or container, not in the JVM heap.

BrowserPool defaults and expected behavior

IronPDF can keep a small number of browser tabs warm between renders to improve performance.

Typical default behavior:

  • BrowserPool.Enabled = true
  • BrowserPool.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 for a short time. Each idle tab may continue holding native memory and subprocess resources until the idle timeout expires.

Depending on the rendered content and environment, each idle tab may retain a noticeable amount of memory. In some workloads, users may observe roughly 50 to 100 MB per idle tab.

This means users may see:

  • memory stays above the pre-render baseline even after rendering stops
  • that elevated baseline can remain for about 30 seconds after the last render
  • memory may then drop in steps as idle tabs are removed

This is expected behavior when BrowserPool is enabled.

How to tune BrowserPool for lower memory usage

If your environment is memory-constrained, BrowserPool settings are the first thing to tune.

Example: 

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 

Option 1: Disable tab pooling completely

Use this when you want the most deterministic cleanup after each render. 

renderer.RenderingOptions.BrowserPool.Enabled = false; 

Option 2: Keep BrowserPool enabled, but do not retain idle tabs

This keeps the feature on but avoids retaining warm tabs between renders. 

renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0; 

Option 3: Reduce the idle timeout

Use this when you want some reuse benefit, but want memory to fall sooner after burst traffic. 

renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10; 

When to use each option

  • Use Enabled = false when memory stability is more important than warm-start performance.
  • Use MaxIdleTabs = 0 when you want deterministic per-render cleanup without fully disabling the BrowserPool feature.
  • Use a lower IdleTimeoutSeconds when your workload comes in bursts and you want memory reclaimed sooner after each burst.

Does this always mean there is a memory leak?

Not necessarily.

With Chromium-based rendering, there are two kinds of reuse to understand:

1. Allocator-level reuse

Chromium may keep native memory pages reserved internally. This is normal and is not something the application directly controls.

2. Tab-level reuse

BrowserPool may keep full browser tabs alive between renders. This is configurable and is often the bigger contributor to the visible memory baseline users notice in monitoring tools.

This is usually expected behavior when:

  • memory rises during rendering
  • memory stabilizes instead of growing without limit
  • memory remains elevated briefly after rendering
  • memory later drops as idle tabs time out, or remains available for later renders

It may need investigation 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

Recommended strategies for long-running applications

If your application must keep memory usage consistently low, start with BrowserPool tuning before moving to process recycling.

A practical order of action is:

  1. limit concurrent rendering
  2. tune or disable BrowserPool
  3. isolate rendering into a separate worker process or container
  4. recycle the worker only if the workload still requires a strict memory ceiling

1. Limit concurrent rendering

Multiple Chromium render jobs running at the same time can increase native memory pressure significantly.

If your application renders PDFs in parallel, reduce the number of concurrent render operations.

Example: 

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();
    }
}

You can wrap your IronPDF render call inside this throttled section so that only a limited number of render jobs run at once.

2. Tune or disable BrowserPool

For many memory-sensitive deployments, this is the most effective first step.

Recommended approaches:

  • disable BrowserPool completely
  • set MaxIdleTabs = 0
  • reduce IdleTimeoutSeconds for bursty workloads

This can reduce the persistent post-render baseline without requiring a full process restart strategy.

3. Run rendering in a separate worker process or container

For production systems, isolating PDF rendering from the main application is still a strong pattern.

Benefits:

  1. the main app remains stable
  2. the rendering worker can be restarted independently
  3. native Chromium memory is fully released when that worker exits

However, before relying on process recycling, test whether disabling BrowserPool or setting MaxIdleTabs = 0 already gives you predictable per-render cleanup without needing full worker restarts.

This approach is especially useful in:

  • Docker or Kubernetes environments
  • background job systems
  • API platforms that need stricter memory isolation

4. Recycle the rendering worker only when necessary

If your environment has a hard memory ceiling and tuning BrowserPool is still not enough, recycling the rendering process or worker container after a certain number of jobs can be a practical solution.

Examples include:

  • restarting a dedicated rendering worker after a fixed batch size
  • rotating engine containers in Kubernetes
  • isolating rendering into a service that can be restarted independently from the main application

This should usually be a later step, not the first one.

Best practice summary

If memory stays high after PDF generation, that does not automatically mean IronPDF has a memory leak.

With Chromium-based rendering, visible memory usage may come from:

  1. Chromium native allocator retention
  2. BrowserPool retaining warm tabs for reuse

For long-running services, the most effective steps are:

  • limit concurrent renders
  • tune BrowserPool settings first
  • disable BrowserPool or set MaxIdleTabs = 0 when deterministic cleanup matters more than warm-start performance
  • lower IdleTimeoutSeconds for bursty workloads
  • isolate rendering in a worker process or container when stricter memory control is needed

When to contact support

Please contact support if you can reproduce all of the following:

  1. memory continues growing without stabilizing under a controlled workload
  2. the issue still happens after limiting concurrency
  3. the issue still happens after disabling BrowserPool or setting MaxIdleTabs = 0
  4. you can reproduce the issue with a minimal sample project

When opening a ticket, include:

  • IronPDF version
  • OS and hosting environment
  • Docker or Kubernetes details, if applicable
  • programming language
  • render frequency and concurrency level
  • memory graphs or monitoring screenshots
  • a minimal reproducible sample