<rmcreative>

RSS

Все заметки с тегами «PHP, утечки»

  1. Утечки памяти в PHP

    12 сентября 2010

    Утечки памяти обычно не беспокоят PHP-разработчиков. Типичное приложение обрабатывает один запрос и работает не больше секунды. После этого вся использованная им память освобождается. Даже если приложение кушает слишком много, максимум, разработчик упирается в memory_limit, выставленный хостером, что решить в общем случае довольно просто: как только переменная становится не нужна, очищаем память, занимаемую ей, при помощи unset.

    Однако, при выполнении ресурсоёмких задач (например, обработки большого количества данных) или запуске PHP как демона проблема утечек встаёт очень остро.

    В PHP 5.2 нет полноценного сборщика мусора. Вместо него используется подсчёт ссылок.

    Все значения переменных хранятся в памяти. И чтобы занимать как можно меньше места, переменные с одинаковыми значениями просто ссылаются на одну и ту же область памяти. При этом количество ссылок подсчитывается и, как только оно становится равно нулю, память освобождается.

    $a = 10; // выделяем область в памяти, одна ссылка
    $b = $a; // две ссылки
    unset($a); // одна ссылка
    $a = 10; // выделяем область в памяти, одна ссылка
    $b = $a; // две ссылки
    $b = 1; // выделяем вторую область в памяти под значение 1, одна ссылка на 1, одна ссылка на 10

    В PHP 5.2 причиной утечек являются циклические ссылки:

    class A {
      private $b;
     
      function __construct(){
        $this->b = new B($this);
      }
    }
     
    class B {
      private $a;
     
      function __construct($a){
        $this->a = $a;
      }
    }
     
    $i=1;
    while($i<=1000){
      $a = new A();
      // 1 ссылка на A ($a).
      // 1 ссылка на B (A::$b).
      // 2 ссылки на A (B::$a).
      unset($a);
      // 1 ссылка на A всё ещё осталась. Память освобождать рановато.
      echo $i."\t".memory_get_usage()."\n";
      $i++;
    }

    Исправляется это явным уничтожением ссылки на B при помощи unset:

    class A {
      private $b;
     
      function __construct(){
        $this->b = new B($this);
      }
     
      function __destruct(){
        unset($this->b);
      }
    }
     
    class B {
      private $a;
     
      function __construct($a){
        $this->a = $a;
      }
    }
     
    $i=1;
    while($i<=1000){
      $a = new A();
      // 1 ссылка на A ($a).
      // 1 ссылка на B (A::$b).
      // 2 ссылки на A (B::$a).
      unset($a);
      // 1 ссылка на A (минус одна в B::__destruct).
      // 0 ссылок на A (unset). Память можно освободить.
      echo $i."\t".memory_get_usage()."\n";
      $i++;
    }

    В PHP 5.3 более умный сборщик мусора, который умеет находить и подчищать последствия использования циклических ссылок. Однако, поиск таких ссылок занимает значительное время и зависит от количества «неподчищенных» ссылок. Плюс к этому работает сборщик не постоянно, а срабатывает только при наполнении буфера ссылок. То есть до его срабатывания какое-то количество памяти всё-таки успевает утекать.

    На заметку. Посмотреть, сколько памяти кушает ваше приложение можно при помощи следующих функций:

    • memory_get_usage() — использованная скриптом память в байтах в момент вызова функции.
    • memory_get_usage(true) — использованная скриптом и менеджером памяти PHP память в байтах в момент вызова функции.
    • memory_get_peak_usage() — максимальное количество памяти в байтах, использованной скриптом с запуска скрипта до момента вызова функции.
    • memory_get_peak_usage(true) — максимальное количество памяти в байтах, использованной скриптом и менеджером памяти PHP с запуска скрипта до момента вызова функции.
    10 комментариев