Understanding PHP-FPM Memory Behavior
Quick answer: PHP-FPM doesn't release memory back to the OS after allocation because memory allocation is expensive. Instead, it keeps the memory reserved for potential reuse. This isn't a memory leak — the memory remains accessible and reusable by PHP.
Understanding PHP's Memory Lifecycle
PHP has a lifecycle similar to what you might see in frameworks like Laravel. PHP's execution goes through several phases, starting with MINIT() and ending with MSHUTDOWN().
During MINIT(), PHP allocates just enough memory to get started using start_memory_manager. This initializes the allocator with a minimal baseline from the OS — around 45KB as an example.
Then, during RINIT() (request initialization), when your application handles a job, performs a large collection operation, or executes a resource-intensive query, PHP needs to allocate more memory (e.g., 124MB). This is when the Zend Memory Manager (ZMM) springs into action.
How Zend Memory Manager Allocates Memory
The ZMM categorizes memory allocations into three types:
- Small allocations: Less than 3KB
- Large allocations: Between 3KB and 2MB
- Huge allocations: Greater than 2MB (these come directly from the OS)
If there's no pre-allocated memory available, huge allocations use
mmapand are handled differently from smaller allocations.
For example, if PHP needs memory, it might request 150MB from the ZMM, which then asks the OS to reserve that space using mmap. But why 150MB when you only needed 124MB?
This is called pre-allocation. PHP requests slightly more memory than immediately needed to reduce the number of expensive mmap and munmap system calls to the OS. This optimization improves performance by batching memory operations.
What Happens After Request Completion
When the request finishes, RSHUTDOWN() is triggered. The garbage collector kicks in, frees up the memory space, and cleans up zvals (PHP's internal value structures).
However — and this is the key point — PHP and ZMM free this memory internally but don't return it to the OS. Why? Because if PHP needs memory again soon, it can reuse what's already allocated instead of requesting more from the OS. Since memory allocation is relatively expensive in terms of system resources, PHP keeps this memory reserved as a performance optimization.
This means whenever PHP needs memory again, it uses what's already available rather than making new system calls. While efficient, it causes PHP to appear to consume more RAM than it's actively using.
What About unset()?
You might be thinking: "But doesn't unset() free memory in PHP?"
Yes, unset() removes references to objects and creates free blocks that can be reused — but these are typically small allocations. However, if enough memory is freed to clear an entire page (ranging from 4KB to 2MB) and clear a complete chunk, only then will PHP return that memory to the OS.
Practical Implications and Best Practices
This behavior explains why frameworks like Laravel implement features such as:
- Lazy loading: Defers loading resources until needed (though don't overuse it or you'll face performance issues)
- Chunking: Processes large datasets in smaller batches
- Pipes: Streams data through transformations efficiently
These patterns help manage memory more effectively by working with PHP's memory management behavior rather than against it.
Conclusion
PHP-FPM's higher-than-expected RAM consumption in production isn't a bug or a memory leak — it's an intentional optimization. By keeping allocated memory reserved rather than constantly requesting and releasing it from the OS, PHP reduces expensive system calls and improves overall performance. Understanding this behavior helps you better monitor and optimize your PHP applications in production environments.

Top comments (0)