No Food for Thought

Memory usage of Apache's PHP children processes

admin Monday December 14, 2015

I ran a PHP benchmark for which I allowed PHP to take as much memory as it wanted. The benchmark worked, but I then realized Apache was using 2 GB of RAM. The parent process was fine, but it turned out the apache2 child process which had run the benchmark was still using 2 GB (RES).

I thought that was abnormal, but I verified on ##php and eventually had confirmation from several people that - to my great surprise - this is not a memory leak. This behavior is expected. And indeed, I can re-run the same benchmark and it will never run out of memory if it succeeded to reserve enough memory the first time. I am not a sysadmin, but that was still quite a shock. I was told PHP has its own memory manager, and only releases memory if the Apache child is restarted. In reality though, other processes (including Apache children) will manage to "steal" memory reserved by idle children. This is surely the part I find most amazing. I am curious to learn how Linux manages that.

So, the memory Apache grants to PHP children will sometimes only be released when these children processes are restarted, but other processes will manage to reclaim that memory if needed. At the very least in our configuration (Debian 8's PHP 5.6.14 on Apache 2.4.10 with prefork MPM).

One important word above is "sometimes". For some reason, children sometimes immediately release their memory. I initially thought it took 2 executions for memory to stick, but a second execution does not always lock. Which is why I would welcome pointers to discussion of this behavior. It seems memory will not be freed if 2 requests come with little idle time in between (seconds).

The following shows well enough an Apache restart freeing 2 GB of RAM:

root@Daphnis:/var/log/apache2# free -h; grep Mem /proc/meminfo; service apache2 restart; free -h; grep Mem /proc/meminfo
             total       used       free     shared    buffers     cached
Mem:          3,0G       2,4G       660M       9,5M       688K        75M
-/+ buffers/cache:       2,3G       736M
Swap:         713M       276M       437M
MemTotal:        3173424 kB
MemFree:          675824 kB
MemAvailable:     634736 kB
             total       used       free     shared    buffers     cached
Mem:          3,0G       216M       2,8G       9,4M       756K        88M
-/+ buffers/cache:       126M       2,9G
Swap:         713M       270M       443M
MemTotal:        3173424 kB
MemFree:         2951552 kB
MemAvailable:    2917400 kB