PHP-garbage collection bepaalt vaak of een hostingstack onder belasting soepel draait of in latentiepieken omvalt. Ik laat zien hoe de collector uitvoeringstijd opslokt, waar hij geheugen bespaart en hoe ik door gerichte tuning meetbaar snellere reacties bereik.
Centrale punten
Dit overzicht Ik vat het samen in een paar kernpunten, zodat je meteen kunt werken aan de dingen die er echt toe doen. Ik geef prioriteit aan meetbaarheid, omdat ik zo beslissingen goed kan valideren en niet in het duister tast. Ik houd rekening met hostingparameters, omdat die een grote invloed hebben op het effect van de GC-instellingen. Ik beoordeel risico's zoals lekken en vertragingen, omdat deze bepalend zijn voor de stabiliteit en snelheid. Ik gebruik de huidige PHP-versies, omdat verbeteringen vanaf PHP 8+ de GC-belasting merkbaar verminderen.
- afweging: Minder GC-runs besparen tijd, meer RAM buffert objecten.
- FPM-tuning: pm.max_children en pm.max_requests regelen de levensduur en lekken.
- OpCache: Minder compilaties verminderen de druk op allocator en GC.
- Sessies: SGC ontlast verzoeken merkbaar via Cron.
- Profilering: Blackfire, Tideways en Xdebug tonen echte hotspots.
Hoe de garbage collector in PHP werkt
PHP gebruikt referentietelling voor de meeste variabelen en geeft cycli door aan de garbage collector. Ik zie hoe de collector cyclische structuren markeert, roots controleert en geheugen vrijmaakt. Hij draait niet bij elk verzoek, maar op basis van triggers en interne heuristiek. In PHP 8.5 verminderen optimalisaties het aantal potentieel verzamelbare objecten, wat betekent dat er minder vaak gescand hoeft te worden. Ik stel gc_status() om runs, verzamelde bytes en root-buffers te controleren.
Triggers en heuristieken begrijpen
In de praktijk begint het verzamelen wanneer de interne root-buffer een drempel overschrijdt, bij het afsluiten van het verzoek of wanneer ik expliciet gc_collect_cycles() oproepen. Lange objectketens met cyclische verwijzingen vullen de rootbuffer sneller. Dit verklaart waarom bepaalde workloads (ORM-zwaar, event-dispatchers, closures met $this-Captures) aanzienlijk meer GC-activiteit vertonen dan eenvoudige scripts. Nieuwere PHP-versies verminderen het aantal kandidaten dat in de root-buffer wordt opgenomen, waardoor de frequentie merkbaar daalt.
Gericht sturen in plaats van blindelings uitschakelen
Ik schakel de verzameling niet volledig uit. In batch-jobs of CLI-workers is het echter de moeite waard om de GC tijdelijk uit te schakelen (gc_disable()), de baan doorrekenen en uiteindelijk gc_enable() plus gc_collect_cycles() uit te voeren. Voor FPM-webverzoeken blijft zend.enable_gc=1 mijn standaardinstelling – anders loop ik het risico op verborgen lekken bij een groeiende RSS.
Invloed op prestaties onder belasting
Profilering toont in projecten regelmatig 10-21% uitvoeringstijd voor het verzamelen, afhankelijk van objectgrafieken en werklast. In afzonderlijke workflows bedroeg de besparing door tijdelijke deactivering tientallen seconden, terwijl het RAM-gebruik matig toenam. Ik beoordeel daarom altijd de afweging: tijd versus geheugen. Frequente GC-triggers veroorzaken vertragingen, die zich bij veel verkeer opstapelen. Goed gedimensioneerde processen verminderen dergelijke pieken en houden de latentie stabiel.
Tail-latenties afvlakken
Ik meet niet alleen het gemiddelde, maar ook p95–p99. Dat is precies waar GC-stalls toeslaan, omdat ze samenvallen met pieken in de objectgrafiek (bijvoorbeeld na cache-misses of cold starts). Maatregelen zoals grotere opcache.interned_strings_buffer, Minder stringduplicatie en kleinere batches verminderen het aantal objecten per verzoek – en daarmee de variantie.
PHP-geheugenbeheer in detail
Referenties en cycli bepalen hoe geheugen stroomt en wanneer de collector ingrijpt. Ik vermijd globale variabelen omdat ze de levensduur verlengen en de grafieken doen groeien. Generatoren in plaats van grote arrays drukken piekbelastingen en houden de verzamelingen klein. Daarnaast controleer ik Geheugenfragmentatie, omdat een gefragmenteerde heap het effectieve gebruik van RAM verzwakt. Goede scopes en het vrijgeven van grote structuren na gebruik houden de verzameling efficiënt.
Typische bronnen voor cycli
- Sluitingendie $this capturen, terwijl het object op zijn beurt listeners vasthoudt.
- Evenementendispatcher met duurzame luisteraarslijsten.
- ORM's met bidirectionele relaties en unit-of-work-caches.
- Globale caches in PHP (singletons), die referenties bevatten en scopes opblazen.
Ik doorbreek dergelijke cycli doelbewust: zwakkere koppeling, levenscyclusreset na batches, bewuste unset() op grote structuren. Waar nodig maak ik gebruik van WeakMap of WeakReference, zodat tijdelijke objectcaches geen permanente belasting worden.
CLI-werknemers en langlopers
Bij wachtrijen of daemons wordt cyclisch opschonen steeds belangrijker. Ik verzamel na N taken (N afhankelijk van de payload 50–500) via gc_collect_cycles() en bekijk de RSS-geschiedenis. Als deze ondanks het verzamelen stijgt, plan ik een zelfstandige herstart van de worker vanaf een drempelwaarde. Dit weerspiegelt de FPM-logica van pm.max_aanvragen in de CLI-wereld.
FPM- en OpCache-tuning, dat de GC ontlast
PHP-FPM bepaalt hoeveel processen parallel actief zijn en hoe lang ze bestaan. Ik bereken pm.max_children grofweg als (totale RAM − 2 GB) / 50 MB per proces en pas dit aan met werkelijke meetwaarden. Via pm.max_requests recycle ik processen regelmatig, zodat lekken geen kans krijgen. OpCache verlaagt de compile-overhead en vermindert stringduplicatie, wat het allocatievolume en daarmee de druk op de verzameling verlaagt. Details werk ik uit in de OpCache-configuratie en bekijk trefpercentages, herstarts en geïnterneerde strings.
Procesmanager: dynamisch versus op aanvraag
pm.dynamisch houdt werknemers warm en vangt piekbelastingen op met een korte wachttijd. pm.ondemand bespaart RAM in fasen met een lage belasting, maar start processen indien nodig – de opstarttijd kan merkbaar zijn in p95. Ik kies het model dat past bij de belastingscurve en test hoe de verandering van invloed is op tail-latenties.
Rekenvoorbeeld en grenzen
Als startpunt levert (RAM − 2 GB) / 50 MB al snel hoge waarden op. Op een host van 16 GB zou dat ongeveer 280 workers zijn. CPU-kernen, externe afhankelijkheden en de werkelijke procesvoetafdruk beperken de realiteit. Ik kalibreer met meetgegevens (RSS per worker onder piekbelasting, p95-latenties) en kom vaak aanzienlijk lager uit om de CPU en IO niet te overbelasten.
OpCache-details met GC-effect
- interned_strings_buffer: Een hogere instelling vermindert stringduplicatie in userland en daarmee de allocatiedruk.
- geheugen_verbruik: Voldoende ruimte voorkomt code-eviction, vermindert het aantal hercompilaties en versnelt warmstarts.
- Voorladen: Vooraf geladen klassen verminderen autoload-overhead en tijdelijke structuren – zorgvuldig dimensioneren.
Aanbevelingen in één oogopslag
Deze tabel bundelt startwaarden, die ik vervolgens nauwkeurig afstem met benchmarks en profilergegevens. Ik pas cijfers aan concrete projecten aan, omdat payloads sterk variëren. De waarden zorgen voor een veilige start zonder uitschieters. Na de uitrol houd ik een belastingstestvenster open en reageer ik op statistieken. Zo blijft de GC-belasting onder controle en de responstijd kort.
| Context | toets | Startwaarde | Tip |
|---|---|---|---|
| Procesmanager | pm.max_kinderen | (RAM − 2 GB) / 50 MB | RAM afwegen tegen concurrency |
| Procesmanager | pm.start_servers | ≈ 25% van max_children | Warme start voor piekfasen |
| Proceslevenscyclus | pm.max_aanvragen | 500–5.000 | Recycling vermindert lekken |
| Geheugen | geheugenlimiet | 256–512 MB | Te klein bevordert Stallen |
| OpCache | opcache.geheugen_verbruik | 128–256 MB | Hoge hitrate bespaart CPU |
| OpCache | opcache.interned_strings_buffer | 16–64 | Strings splitsen vermindert RAM |
| GC | zend.enable_gc | 1 | Meetbaar laten, niet blindelings uitschakelen |
Sessie Garbage Collection gericht aansturen
Sessies hebben een eigen verwijderingsfunctie, die bij standaardconfiguraties gebruikmaakt van willekeur. Ik deactiveer de waarschijnlijkheid via session.gc_probability=0 en roep de opruimfunctie op via Cron. Zo blokkeert geen enkel gebruikersverzoek het verwijderen van duizenden bestanden. Ik plan de looptijd om de 15-30 minuten, afhankelijk van session.gc_maxlifetime. Belangrijk voordeel: de responstijd van de website blijft stabiel, terwijl de opschoning op een ander moment plaatsvindt.
Sessieontwerp en GC-afdruk
Ik houd sessies klein en serialiseer er geen grote objectbomen in. Extern opgeslagen sessies met lage latentie maken het verzoekpad vloeiender, omdat bestandstoegang en opruimacties geen achterstand in de webtier veroorzaken. Het is belangrijk om de levensduur (session.gc_maxlifetime) te koppelen aan het gebruiksgedrag en opschoningsprocessen te synchroniseren met off-peak-vensters.
Profilering en monitoring: cijfers in plaats van intuïtie
profiler zoals Blackfire of Tideways laten zien of het verzamelen echt vertraagt. Ik vergelijk runs met actieve GC en met tijdelijke deactivering in een geïsoleerde taak. Xdebug levert GC-statistieken die ik gebruik voor diepgaande analyses. Belangrijke indicatoren zijn het aantal runs, verzamelde cycli en tijd per cyclus. Met herhaalde benchmarks bescherm ik mezelf tegen uitschieters en neem ik betrouwbare beslissingen.
Meet-playbook
- Baseline zonder wijzigingen opnemen: p50/p95, RSS per werknemer, gc_status()-waarden.
- Een variabele wijzigen (bijv. pm.max_aanvragen of interned_strings_buffer), opnieuw meten.
- Vergelijking met identieke hoeveelheid gegevens en opwarmen, minimaal 3 herhalingen.
- Stapsgewijze uitrol, nauwlettende monitoring, snelle omkeerbaarheid garanderen.
Limieten, memory_limit en RAM-berekening
geheugenlimiet stelt het maximum per proces vast en beïnvloedt indirect de frequentie van verzamelingen. Ik plan eerst de werkelijke footprint: baseline, pieken, plus OpCache en C-extensies. Vervolgens kies ik een maximum met ruimte voor kortstondige piekbelastingen, doorgaans 256-512 MB. Voor details over de interactie verwijs ik naar het artikel over PHP geheugenlimiet, die de neveneffecten transparant maakt. Een zinvolle limiet voorkomt out-of-memory-fouten zonder de GC-belasting onnodig te verhogen.
Container- en NUMA-invloeden
In containers telt het cgroup-plafond, niet alleen het host-RAM. Ik stel geheugenlimiet en pm.max_kinderen op de containergrenzen en houd veiligheidsafstanden aan, zodat de OOM-killer niet toeslaat. Bij grote hosts met NUMA zorg ik ervoor dat processen niet te dicht op elkaar staan, zodat de toegang tot het geheugen consistent snel blijft.
Architectuurtips voor high-traffic
Schalen Ik los dit in stappen op: eerst de procesparameters, dan de horizontale distributie. Read-heavy workloads profiteren sterk van OpCache en korte opstarttijden. Voor schrijfpaden kapsel ik dure bewerkingen asynchroon in, zodat het verzoek licht blijft. Caching dicht bij PHP vermindert de hoeveelheid objecten en daarmee de controle-inspanning van de verzameling. Goede hosters met krachtig RAM en een schone FPM-setup, zoals webhoster.de, maken deze aanpak aanzienlijk eenvoudiger.
Code- en build-aspecten met GC-impact
- Composer-autoloader optimaliseren: Minder bestandstoegangen, kleinere tijdelijke arrays, stabielere p95.
- Houd de payload klein: DTO's in plaats van enorme arrays, streaming in plaats van bulk.
- Strikte scopes: Functie- in plaats van bestandsbereik, variabelen vrijgeven na gebruik.
Deze ogenschijnlijk kleine dingen verminderen toewijzingen en cyclusgroottes – dit heeft een directe invloed op het werk van de collector.
Foutpatronen en anti-patronen
Symptomen Ik herken dit aan zigzaglatenties, pieken in de CPU-belasting en stijgende RSS-waarden per FPM-worker. Veelvoorkomende oorzaken zijn grote arrays als verzamelbakken, globale caches in PHP en het ontbreken van procesherstarts. Ook het opschonen van sessies in het verzoekpad zorgt voor trage reacties. Ik pak dit aan met generators, kleinere batches en duidelijke levenscycli. Daarnaast controleer ik of externe services retries activeren die verborgen objectstromen genereren.
Praktische checklist
- gc_status() Regelmatig loggen: runs, tijd per run, root-buffergebruik.
- pm.max_aanvragen zo kiezen dat RSS stabiel blijft.
- interned_strings_buffer hoog genoeg om duplicaten te voorkomen.
- batchgroottes zo snijden dat er geen massieve puntgrafen ontstaan.
- Sessies ontkoppeld opschonen, niet in het verzoek.
Resultaten sorteren: wat echt telt
Onder de streep PHP Garbage Collection zorgt voor merkbare stabiliteit als ik het bewust aanstuur in plaats van het tegen te werken. Ik combineer een lagere collectorfrequentie met voldoende RAM en gebruik FPM-recycling om lekken te laten verdampen. OpCache en kleinere datasets verminderen de druk op de heap en helpen om vertragingen te voorkomen. Ik laat sessies opschonen door Cron, zodat verzoeken vrij kunnen ademen. Met metrics en profiling verzeker ik het effect en houd ik de responstijden betrouwbaar laag.


