I have a server which has 2 PHP scripts that run continuously in the background. One of those scripts, for some reason, is not releasing memory and it's making the other script fail because eventually I have no available RAM left on the server.
I created a pretty simple POC so you can check by yourself. Basically I have a function that checks the available RAM memory (using free
command) three times, and after the first time I execute the function that makes memory be consumed and never released. I even tried gc_collect_cycles
but nothing happens.
What amazes me, is that after the script finishes completely, if I go to my SSH and execute "free" all the used memory is indeed released however if I add a long sleep at the end o my script, the memory will keep being used. Notice that inside the function test()
a local variable is created and it is explicitly unsetted, but for some reason PHP still holds the memory.
<?php
set_time_limit(0);
ini_set("memory_limit","-1");
function show_available_ram() {
$temp1111 = shell_exec('sudo free -m');
preg_match("/Mem: +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +[0-9]+ +([0-9]+)/",$temp1111,$temp2222);
return $temp2222[1] . " MB";
}
function test() {
$temp3333 = array();
for ($i=0;$i<1000000;$i++) {
$temp3333[] = array("key1" => md5($i), "key2" => $i);
}
unset($temp3333);
}
echo "\nAAA: " . show_available_ram();
test();
sleep(2);
echo "\nBBB: " . show_available_ram();
//I know it should not be needed the line below, but I am trying it anyway but it does not help.
gc_collect_cycles();
sleep(2);
echo "\nCCC: " . show_available_ram();
?>
The code above outputs:
AAA: 1297 MB
BBB: 873 MB
CCC: 869 MB
So you can clearly see memory being used (on BBB
and CCC
but not released even after test()
has already finished.
In the code above I use a few sleep
so PHP can have time to garbage collect, but it does not help at all.
The memory is cleared only when the entire PHP script finishes executing and the PHP process terminates. That's when the operating system reclaims all the memory allocated to that process. This is why you see the memory freed when you remove the sleep()
and the script completes quickly.
One solution to the problem is to break down the process in chunks. Based on the sample script you provided, below is the updated script that does the job in batches.
<?php
function process_chunk($chunk_size) {
$data_chunk = array();
for ($i = 0; $i < $chunk_size; $i++) {
$data_chunk[] = array("key1" => md5($i), "key2" => $i);
}
// Process the $data_chunk here (e.g., save to database, etc.)
unset($data_chunk); // Explicitly unset after processing
}
$chunk_size = 100000; // Process in chunks of 100,000
echo "\nAAA: " . show_available_ram();
for ($j = 0; $j < 10; $j++) { // Process 10 chunks which can be increased
process_chunk($chunk_size);
echo "\nProcessing chunk " . ($j + 1) . " - Available RAM: " . show_available_ram();
// Optionally add a small sleep to reduce CPU load. 10 milliseconds
usleep( 10 * 1000 );
}
echo "\nBBB (After processing): " . show_available_ram();
?>