Uso de memoria CEF/Chromium en aplicaciones de larga ejecución
IronPDF genera con Chromium, por lo que la memoria del proceso puede permanecer alta por un corto período después de la generación de PDF, incluso cuando los documentos se eliminan y GC.Collect() se ejecuta. Dos fuentes están en juego: el asignador nativo no administrado de Chromium y la agrupación de pestañas del navegador de IronPDF. En la mayoría de los casos esto es esperado, y no significa automáticamente que haya una fuga de memoria.
Esto se aplica a IronPDF y IronPdfEngine en Windows y Linux, a través de Docker, Kubernetes, VM y entornos alojados en servidores, así como a .NET, Java y cualquier aplicación que use renderizado basado en Chromium.
Por qué la memoria permanece alta
Dos mecanismos distintos mantienen la memoria alta después de que un renderizado termina.
Retención del asignador nativo de Chromium: Chromium usa una gran cantidad de memoria no administrada que vive fuera del recolector de basura de .NET. Cuando un renderizado se completa, Chromium a menudo mantiene esa memoria nativa reservada para reutilización en lugar de devolverla al sistema operativo inmediatamente. Así, tus objetos PdfDocument pueden ser eliminados correctamente, el tiempo de ejecución puede recolectar objetos administrados, y el proceso o la memoria del contenedor en general aún puede leer alto. Esto es normal para motores basados en Chromium.
Reutilización de pestañas en el BrowserPool: IronPDF puede mantener pestañas del navegador inactivas vivas después de un renderizado para que el próximo comience más rápido. Esas pestañas mantienen sus subprocesos de renderizado y el estado del DOM por un corto tiempo, creando un nivel de memoria visible incluso cuando no se está renderizando nada. Con el agrupamiento activado, típicamente verás que la memoria aumenta durante el renderizado, se mantiene en un nivel base después de que se completa, luego cae en pasos a medida que las pestañas inactivas se agotan y se recolectan.
Por Qué GC.Collect() No Ayuda
GC.Collect() solo recupera la memoria .NET administrada. No obliga a Chromium a devolver memoria nativa al sistema operativo, y no desmantela las pestañas del BrowserPool caliente que se mantienen vivas intencionalmente para reutilización. Incluso con la eliminación correcta y una recolección forzada, las figuras mostradas por el Administrador de tareas, Docker, Kubernetes, o herramientas como New Relic pueden no caer de inmediato.
El efecto es más visible en aplicaciones de larga duración, entornos de servicios compartidos e implementaciones en Docker o Kubernetes. Para integraciones de Java que usan IronPdfEngine, la presión generalmente se muestra en el proceso o contenedor del motor en lugar del heap de JVM.
Configuraciones predeterminadas de BrowserPool
IronPDF mantiene un pequeño número de pestañas calientes entre renderizados para mejorar el rendimiento. Los valores predeterminados son:
BrowserPool.Enabled = trueBrowserPool.MaxIdleTabs = min(max(ProcessorCount / 2, 1), 4)BrowserPool.IdleTimeoutSeconds = 30
Debido a esto, incluso sin renderizados activos, el proceso puede mantener de 1 a 4 pestañas inactivas vivas brevemente, cada una sosteniendo memoria nativa y recursos de subprocesos hasta que el tiempo de espera inactivo expire. Dependiendo del contenido renderizado y el entorno, una pestaña inactiva puede retener una cantidad notable, aproximadamente de 50 a 100 MB en algunas cargas de trabajo. Ese nivel elevado puede persistir durante unos 30 segundos después del último renderizado antes de caer en pasos.
Solución
Si tu entorno tiene restricciones de memoria, las configuraciones de BrowserPool son lo primero que debes ajustar. Los valores predeterminados se ven así:
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
Opción 1: Desactivar completamente el agrupamiento de pestañas
Elige esto cuando quieras una limpieza más determinista después de cada renderizado.
renderer.RenderingOptions.BrowserPool.Enabled = false;
renderer.RenderingOptions.BrowserPool.Enabled = false;
renderer.RenderingOptions.BrowserPool.Enabled = False
Opción 2: Mantén el BrowserPool habilitado sin retener pestañas inactivas
Deja la función encendida pero evita sostener pestañas calientes entre renderizados, ofreciendo una limpieza determinista por renderizado.
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0;
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0;
Opción 3: Reduce el tiempo de espera inactivo
Un término medio: mantén algunos beneficios de reutilización, pero permite que la memoria caiga antes después del tráfico en ráfagas.
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10;
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10;
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10
Elegir entre ellos:
Enabled = false: la estabilidad de la memoria es más importante que el rendimiento de arranque en caliente.MaxIdleTabs = 0: deseas limpieza por cada renderizado de forma determinista sin deshabilitar completamente la función.- Reducir
IdleTimeoutSeconds: tu carga de trabajo llega en ráfagas y deseas que la memoria se recupere más pronto después de cada una.
Estrategias para aplicaciones de larga duración
Si tu aplicación debe mantener la memoria consistentemente baja, trabaja a través de estas en orden, comenzando con el ajuste de BrowserPool antes de recurrir al reciclaje de procesos.
1. Limita el renderizado concurrente
Varios trabajos de renderizado de Chromium ejecutándose a la vez aumentan la presión de memoria nativa significativamente. Si renderizas en paralelo, limita cuántos se ejecutan concurrentemente con un semáforo:
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
Envuelve tu llamada de renderizado de IronPDF dentro de la sección controlada por límite para que solo un número fijo de trabajos se ejecuten a la vez.
2. Ajusta o desactiva BrowserPool
Para muchas implementaciones sensibles a la memoria, este es el primer paso más efectivo. Deshabilita completamente BrowserPool, establece MaxIdleTabs = 0 o reduce IdleTimeoutSeconds para cargas de trabajo explosivas. Cualquiera de estas puede reducir la base persistente post-renderizado sin una estrategia de reinicio completo.
3. Aisla el renderizado en un trabajador separado
Mover el renderizado de PDF fuera de la aplicación principal es un patrón fuerte para sistemas de producción. La aplicación principal permanece estable, el trabajador de renderizado puede reiniciarse por su cuenta, y la memoria nativa de Chromium se libera completamente cuando ese trabajador sale. Antes de confiar en el reciclaje, confirma si deshabilitar BrowserPool o establecer MaxIdleTabs = 0 ya te ofrece limpieza predecible por cada renderizado. El enfoque de aislamiento da sus frutos principalmente en entornos de Docker o Kubernetes, sistemas de trabajos en segundo plano y plataformas de API que necesitan un aislamiento de memoria más estricto.
4. Recicla el trabajador solo cuando sea necesario
Donde hay un techo de memoria rígido y el ajuste de BrowserPool aún queda corto, recicla el proceso de renderizado o el contenedor del trabajador después de un número fijo de trabajos. Eso podría significar reiniciar un trabajador dedicado después de un tamaño de lote fijo, rotar contenedores de motores en Kubernetes o aislar el renderizado en un servicio que pueda reiniciarse independientemente. Trata esto como un paso posterior, no tu primer movimiento.
Cuándo podría ser una fuga
La memoria elevada después del renderizado no significa automáticamente que haya una fuga. La renderización basada en Chromium tiene dos tipos de reutilización para entender: reutilización a nivel del asignador, donde Chromium reserva páginas de memoria nativa internamente (normal, no directamente controlable), y reutilización a nivel de pestaña, donde BrowserPool mantiene pestañas completas vivas entre renderizados (configurable y a menudo el mayor contribuyente a lo que ves en las herramientas de monitoreo).
El patrón suele ser esperado cuando la memoria aumenta durante el renderizado, se estabiliza en lugar de crecer sin límite, permanece elevada brevemente después, y luego cae a medida que las pestañas inactivas caducan o se mantienen disponibles para el próximo renderizado.
Investiga más cuando:
- la memoria sigue aumentando sin estabilizarse bajo una carga de trabajo similar
- la memoria sigue creciendo incluso después de reducir la concurrencia
- la memoria sigue creciendo incluso después de deshabilitar BrowserPool o establecer
MaxIdleTabs = 0 - una reproducción mínima muestra el mismo patrón con el tiempo con una entrada controlada
Cuándo contactar con el soporte
Comunícate con soporte técnico si puedes reproducir todos los siguientes problemas:
- la memoria sigue creciendo sin estabilizarse bajo una carga de trabajo controlada
- el problema persiste después de limitar la concurrencia
- el problema persiste después de deshabilitar BrowserPool o establecer
MaxIdleTabs = 0 - puedes reproducirlo con un proyecto de muestra mínimo
Al abrir un ticket, incluye tu versión de IronPDF, sistema operativo y entorno de alojamiento, detalles de Docker o Kubernetes si es aplicable, lenguaje de programación, frecuencia de renderizado y nivel de concurrencia, gráficos de memoria o capturas de pantalla de monitoreo, y un ejemplo mínimo reproducible.

