When examining the top
output, we need to distinguish between different memory measurements:
VIRT - Virtual memory (total address space) RES - Resident memory (physical RAM used) SHR - Shared memory (shared between processes) %MEM - Percentage of physical RAM used
The apparent memory "overcommitment" occurs because PHP-FPM processes typically share:
- Opcode cache (APC/OPcache)
- PHP binary itself
- Common libraries (libc, etc.)
In your output, notice the SHR
column shows ~290MB shared per process. This means only about 15MB (307MB RES - 292MB SHR) is truly unique to each process.
To get the real memory consumption:
Unique memory per process = RES - SHR Total unique memory = (RES - SHR) * process_count Shared memory = SHR (counted only once system-wide)
To better manage memory, consider these php-fpm.conf tweaks:
; Limits process memory usage php_admin_value[memory_limit] = 128M ; Controls how many processes can spawn pm.max_children = 30 pm.start_servers = 5 pm.min_spare_servers = 5 pm.max_spare_servers = 10 ; Helps with shared memory efficiency opcache.enable=1 opcache.memory_consumption=128 opcache.interned_strings_buffer=16
Use pmap
to inspect shared memory segments:
pmap -x <pid> | grep -i shared smem -t -k -P php-fpm
htop
(press F2 to enable shared memory column)glances
(shows memory breakdown)ps_mem.py
(accurately calculates shared memory)
Practical code-level improvements:
// Instead of loading entire datasets: $hugeArray = getAllRecords(); // Use generators or pagination: function getRecordsGenerator() { $offset = 0; $limit = 100; do { $batch = getBatch($offset, $limit); foreach ($batch as $record) { yield $record; } $offset += $limit; } while (!empty($batch)); }
When examining the top output showing multiple PHP-FPM processes each consuming ~30% of memory, it's crucial to understand three key columns:
VIRT - Virtual memory (total address space allocated)
RES - Resident memory (physical RAM actually used)
SHR - Shared memory (portion of RES shared between processes)
%MEM - Percentage of physical RAM used (RES/total RAM)
The apparent memory "overcommitment" occurs because:
- Shared libraries (like PHP core) are loaded once but counted for each process
- PHP's OPcache and common extensions create shared memory segments
- Copy-on-write mechanisms allow memory sharing until modifications occur
In your case, notice how SHR values (292m) are nearly identical across processes - this is shared memory being reported multiple times.
To get the real memory consumption:
Actual RAM Used = (Sum of RES) - (Sum of SHR * (n-1)/n)
For two PHP processes with RES=300m and SHR=290m:
Actual = (300m + 300m) - (290m * 1/2) ≈ 455m (not 600m)
Instead of top, consider these alternatives:
# smem - shows proportional vs unique memory
smem -t -P php-fpm
# pmap - reveals shared memory mapping details
pmap -x [PID] | grep -i shared
# ps with custom output
ps -eo pid,comm,rss,pmem,shared | grep php-fpm
Configuration adjustments in php-fpm.conf:
; Reduce process count
pm.max_children = 30 → 15
; Optimize shared memory
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
; Enable early garbage collection
php_admin_value[memory_limit] = 256M
php_admin_value[opcache.revalidate_freq] = 60
This PHP snippet helps identify memory-hungry components:
<?php
function track_memory($label) {
static $last = 0;
$current = memory_get_usage(true);
printf("%s: %+d KB\n", $label, ($current - $last)/1024);
$last = $current;
}
track_memory("Start");
// Your code segments here...
track_memory("After heavy operation");
Linux provides different memory accounting modes via /proc/meminfo
:
# Check available memory accounting
grep -E 'MemTotal|MemFree|Buffers|Cached|Slab|SReclaimable' /proc/meminfo
# Compare with kernel's view of process memory
cat /proc/[PID]/smaps | grep -i pss