Memory leaks in long-running PHP processes: what to watch for
Running a PHP worker process that processes a queue. After 6 hours, RSS is 800MB, started at 90MB. The leak is somewhere but profiling with Xdebug memory tracking is impractical at this scale.
What strategies do you use to find and fix memory leaks in daemon-style PHP processes?
Blackfire works on CLI processes too. Run the worker, profile a slice of it, look for growing reference cycles in the timeline. Reference cycles that PHP GC does not collect on time are the most common cause.
The most common sources I have seen: event listeners that accumulate closures, ORM identity maps that never get cleared, monolog handlers buffering in memory, static arrays used as caches with no eviction.
Explicitly call gc_collect_cycles() every N iterations during development and watch if memory drops. If it does, you have a reference cycle. If it does not, you have an actual growing data structure.
In Swoole contexts: anything stored in a static property persists for the lifetime of the process across all coroutines. That is the first place to look. Logger instances, config arrays, caches.
We added memory_get_usage() logging every 100 jobs with a warning threshold. Found that one job type was loading full Eloquent collections without chunking. Classic.
WeakReference was added in PHP 8.0 and helps with cache-style patterns. If you hold a cache of objects that nothing else references, wrap them in WeakReference so GC can collect them.
If you cannot fix the leak quickly, pm.max_requests on FPM workers or a restart threshold in your worker loop (restart after N jobs) is a pragmatic mitigation.