Zużycie pamięci CEF/Chromium w długodziałających aplikacjach
IronPDF generuje w Chromium, więc pamięć procesu może pozostać wysoka przez krótki okres po wygenerowaniu PDF, nawet gdy dokumenty są zakończone i uruchomiona jest funkcja GC.Collect(). Dwa źródła odgrywają rolę: niezrządzany natywny alokator Chromium i puli kart przeglądarki IronPDF. W większości przypadków jest to oczekiwane i nie oznacza automatycznie, że mamy do czynienia z wyciekiem pamięci.
Dotyczy to IronPDF i IronPdfEngine na Windows i Linux, w środowiskach Docker, Kubernetes, VM i serwerowych, oraz dla .NET, Java i każdej aplikacji używającej renderowania opartego na Chromium.
Dlaczego pamięć pozostaje podwyższona
Dwa różne mechanizmy utrzymują pamięć po zakończeniu renderowania.
Zatrzymywanie alokatora natywnego Chromium: Chromium używa dużej ilości niezrządzanej pamięci, która istnieje poza kolektorem śmieci .NET. Gdy renderowanie się kończy, Chromium często utrzymuje tę natywną pamięć zarezerwowaną do ponownego użycia zamiast natychmiastowego zwracania jej do systemu operacyjnego. PdfDocument obiekty mogą być poprawnie zakończone, środowisko może zbierać zarządzane obiekty, a całkowita pamięć procesu lub kontenera nadal może być wysoka. Jest to normalne dla silników opartych na Chromium.
Reuse kart przeglądarki w BrowserPool: IronPDF może utrzymywać niewykorzystane karty przeglądarki przy życiu po renderze, aby następny mógł uruchomić się szybciej. Te karty utrzymują swoje podprocesy renderujące i stan DOM przez krótki czas, tworząc widoczny bazowy poziom pamięci, nawet gdy nic nie jest renderowane. Gdy pooling jest włączony, zazwyczaj obserwujesz wzrost pamięci podczas renderowania, utrzymanie na poziomie bazowym po jego zakończeniu, a następnie spadek w krokach gdy bezczynne karty kończą się i są zbierane.
Dlaczego GC.Collect() nie pomaga
GC.Collect() odzyskuje tylko zarządzaną pamięć .NET. Nie wymusza na Chromium oddania natywnej pamięci systemowi operacyjnemu i nie zamyka utrzymywanych ciepłych kart BrowserPool, które są celowo utrzymywane przy życiu do ponownego użycia. Nawet przy poprawnym zakończeniu i wymuszonym zebraniu, liczby pokazane przez Menadżera Zadań, Docker, Kubernetes, lub narzędzia takie jak New Relic mogą nie spadać od razu.
Efekt jest najbardziej widoczny w długodziałających aplikacjach, środowiskach udostępniania usług oraz wdrożeniach Docker lub Kubernetes. Dla integracji Java z IronPdfEngine, nacisk zazwyczaj występuje w procesie silnika lub kontenerze, raczej niż na stercie JVM.
Domyślne ustawienia BrowserPool
IronPDF utrzymuje niewielką liczbę kart przeglądarki w stanie gotowości między renderowaniami, aby poprawić wydajność. Domyślne ustawienia to:
BrowserPool.Enabled = trueBrowserPool.MaxIdleTabs = min(max(ProcessorCount / 2, 1), 4)BrowserPool.IdleTimeoutSeconds = 30
Z tego powodu, nawet bez aktywnych renderów proces może utrzymywać 1 do 4 bezczynnych kart przy życiu przez krótko, każda trzymająca natywną pamięć i zasoby podprocesów aż do wygaśnięcia czasu bezczynności. W zależności od renderowanej zawartości i środowiska, bezczynna karta może utrzymać zauważalną ilość, około 50 do 100 MB w niektórych obciążeniach. Ten podwyższony poziom bazowy może utrzymywać się przez około 30 sekund po ostatnim renderowaniu zanim spadnie w krokach.
Rozwiązanie
Jeśli twoje środowisko ma ograniczoną pamięć, pierwszą rzeczą do dostrojenia są ustawienia BrowserPool. Domyślne wygląda to tak:
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
Opcja 1: Całkowite wyłączenie poolingu kart
Sięgnij po to, gdy chcesz najbardziej deterministyczne oczyszczanie po każdym renderowaniu.
renderer.RenderingOptions.BrowserPool.Enabled = false;
renderer.RenderingOptions.BrowserPool.Enabled = false;
renderer.RenderingOptions.BrowserPool.Enabled = False
Opcja 2: Pozostaw BrowserPool włączony, ale bez utrzymywania bezczynnych kart
Pozostawia funkcję włączoną ale unika utrzymania ciepłych kart między renderowaniami, dając deterministyczne czyszczenie per-render.
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0;
renderer.RenderingOptions.BrowserPool.MaxIdleTabs = 0;
Opcja 3: Zmniejsz czas bezczynności
Środkowe rozwiązanie: utrzymuj pewną korzyść z ponownego użycia, ale pozwól, aby pamięć spadła szybciej po ruchu szczytowym.
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10;
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10;
renderer.RenderingOptions.BrowserPool.IdleTimeoutSeconds = 10
Wybierając między nimi:
Enabled = false: stabilność pamięci jest ważniejsza niż wydajność rozruchu na ciepło.MaxIdleTabs = 0: chcesz deterministycznego czyszczenia po każdym renderze bez całkowitego wyłączania funkcji.- Obniż
IdleTimeoutSeconds: twoje obciążenie przybywa w pacy drgawiących i chcesz, aby pamięć była szybko odzyskana po każdym z nich.
Strategie dla długodziałających aplikacji
Jeśli twoja aplikacja musi utrzymywać pamięć na stałym niskim poziomie, przeanalizuj te kroki, zaczynając od dostrojenia BrowserPool zanim sięgniesz po recykling procesów.
1. Ogranicz równoczesne renderowanie
Kilka jednoczesnych zadań renderowania Chromium gwałtownie zwiększa nacisk na natywną pamięć. Jeśli renderujesz równolegle, limituj ile zadań jest uruchamianych jednocześnie za pomocą semaforu:
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
W okół sekcji z ograniczeniami uruchom swój wywołań IronPDF, aby jednocześnie uruchamiać tylko stałą liczbę zadań.
2. Dostosuj lub wyłącz BrowserPool
Dla wielu pamięcio-czułych wdrożeń jest to najbardziej efektywny pierwszy krok. Całkowicie wyłącz BrowserPool, ustaw MaxIdleTabs = 0 lub obniż IdleTimeoutSeconds dla wymagających obciążeń. Każda z tych opcji może zmniejszyć utrzymujący się poziom po renderowaniu bez strategii pełnego restartu.
3. Izoluj renderowanie w osobnym pracowniku
Przeniesienie renderowania PDF z głównej aplikacji to mocny wzorzec dla systemów produkcyjnych. Główna aplikacja pozostaje stabilna, pracownik renderowania może być restartowany samodzielnie, a natywna pamięć Chromium jest w pełni zwalniana, gdy ten pracownik wychodzi. Zanim polegasz na recyklingu, potwierdź, czy wyłączenie BrowserPool lub ustawienie MaxIdleTabs = 0 już daje ci przewidywalne czyszczenie per-render. Podejście izolacyjne opłaca się najbardziej w środowiskach Docker lub Kubernetes, systemach pracy w tle i platformach API, które potrzebują ściślejszej izolacji pamięci.
4. Recykluj pracownika tylko gdy konieczne
Gdzie istnieje twardy limit pamięci, a dostrojenie BrowserPool wciąż nie wystarcza, recykluj proces renderowania lub kontener pracownika po ustalonej liczbie zadań. Może to oznaczać ponowne uruchomienie dedykowanego pracownika po ustalonym rozmiarze partii, rotację kontenerów silnika w Kubernetes, lub izolowanie renderowania do usługi, która może być ponownie uruchomiona niezależnie. Traktuj to jako późniejszy krok, nie jako twój pierwszy ruch.
Gdy może to być wyciek
Podwyższona pamięć po renderowaniu nie oznacza automatycznie wycieku. Renderowanie oparte na Chromium ma dwa rodzaje ponownego użycia do zrozumienia: ponowne użycie na poziomie alokatora, gdzie Chromium rezerwuje natywne strony pamięci wewnętrznie (normalne, nie bezpośrednio kontrolowalne), i ponowne użycie na poziomie karty, gdzie BrowserPool utrzymuje pełne karty przy życiu między renderowaniami (konfigurowalne, i często większy współuczestnik tego co widzisz w narzędziach monitorujących).
Wzorzec jest zazwyczaj oczekiwany, gdy pamięć rośnie podczas renderowania, stabilizuje się zamiast rosnąć bez ograniczenia, pozostaje podwyższona przez krótko potem i później spada, gdy bezczynne karty kończą się lub pozostają dostępne do następnego renderu.
Badaj dalej gdy:
- pamięć wciąż rośnie bez stabilizacji przy takim samym obciążeniu
- pamięć utrzymuje się nawet po redukcji równoczesności
- pamięć utrzymuje się nawet po wyłączeniu BrowserPool lub ustawieniu
MaxIdleTabs = 0 - minimalna reprodukcja pokazuje ten sam wzorzec z upływem czasu przy kontrolowanym wejściu
Kiedy skontaktować się z wsparciem
Skontaktuj się z wsparciem technicznym jeżeli możesz odtworzyć wszystkie poniższe:
- pamięć nadal rośnie bez stabilizacji przy kontrolowanym obciążeniu
- problem utrzymuje się po ograniczeniu równoczesności
- problem utrzymuje się po wyłączeniu BrowserPool lub ustawieniu
MaxIdleTabs = 0 - możesz go odtworzyć przy użyciu minimalnego projektu próbki
Podczas otwierania zgłoszenia, dołącz swoją wersję IronPDF, system operacyjny i środowisko hostingowe, szczegóły Docker lub Kubernetes jeśli dotyczy, język programowania, częstotliwość renderowania i poziom równoczesności, grafy pamięci lub zrzuty ekranu z monitorowania i minimalną próbkę odtwarzania.

