Uso de memoria CEF/Chromium en aplicaciones de larga ejecución

This article was translated from English: Does it need improvement?
Translated
View the article in English

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 = true
  • BrowserPool.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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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;
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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
$vbLabelText   $csharpLabel

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:

  1. la memoria sigue creciendo sin estabilizarse bajo una carga de trabajo controlada
  2. el problema persiste después de limitar la concurrencia
  3. el problema persiste después de deshabilitar BrowserPool o establecer MaxIdleTabs = 0
  4. 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.

Curtis Chau
Escritor Técnico

Curtis Chau tiene una licenciatura en Ciencias de la Computación (Carleton University) y se especializa en el desarrollo front-end con experiencia en Node.js, TypeScript, JavaScript y React. Apasionado por crear interfaces de usuario intuitivas y estéticamente agradables, disfruta trabajando con frameworks modernos y creando manuales bien ...

Leer más
¿Listo para empezar?
Nuget Descargas 20,296,129 | Versión: 2026.7 recién publicada
Still Scrolling Icon

¿Aún desplazándote?

¿Quieres una prueba rápida? PM > Install-Package IronPdf
ejecutar una muestra Mira cómo tu HTML se convierte en PDF.