Montée en charge de PHP : Fatal error: Allowed memory size exhausted

Ruby logo

PHP est historiquement un language de script procédural. Dépassé par son succès, le langage n’était pas, à l’origine, conçu pour être le plus utiliser pour la réalisation de sites web dynamiques. La révolution objet c’est faite bien plus tard avec PHP4 et 5. Il souffre aujourd’hui de beaucoup de problème de fuite de mémoire.

Si vous faites de la programmation orientée objet sur PHP5.2 ou inférieur, et que vous avez déjà utilisé de l’inversion de contrôle, vous risquez, en cas de traitement lourd sur des objets, de tombé sur le message d’erreur suivant :


Fatal error: Allowed memory size of 50331648 bytes exhausted (tried to allocate 72 bytes)

Il indique que vous avez utilisez toute la mémoire disponible sur votre machine.
Il est intéressant de savoir qu’avant la version 5.3, PHP ne possédait pas de garbage collector. Dans certains cas, on peut se retrouver avec une fuite de mémoire si on ne prend pas garde à bien gérer la destruction des objets que l’on a designés.

Effectivement, PHP gère très mal les références cycliques, qui sont nécessaires pour créer l’IOC.

Sans aller dans les détails d’une architecture utilisant l’inversion de contrôle, Prenons un simple exemple de référence cycliques. Donc, en claire, deux objets qui ont chacun une référence vers l’autre.

Si vous avez un objet A et un object B.
A possède en propriété une instance de B.
B possède en propriété l’instance de A.

Quand vous détruisez A, PHP ne détruit que la référence vers B. Donc B persiste en mémoire.

En tant normal, un appel à la fonction unset permet de libérer l’allocation mémoire d’une variable. Seulement, ici rien n’y fait, vous pouvez tester le script suivant et vous vous apercevrez que la quantité de mémoire ne cesse de monter.

<?php
class A {
   var $propB;
   public function __construct() {
      $this->propB = new B($this);
   }
}
 
class B {
   var $propA;
   public function __construct($instanceA){
      $this->propA = $instanceA;
   }
}
while(true){
   new A();
   echo round(memory_get_usage(true)/1048576,2)."MB\n";
}
 
?>

Il existe au même titre que la méthode __construct, une méthode __destruct qui est appelée automatiquement à la destruction de l’objet.

On peut donc changer son comportement en définissant la méthode de la classe de l’object à détruire. Il faut explicitement détruire l’instance de la classe B contenu dans la propriété propB de la classe A.

<?php
class A {
   var $propB;
   public function __construct() {
      $this->propB = new B($this);
  }
  public  function __destruct(){
      $this->propB->__destruct();
  }
}
 
class  B {
   var $propA;
   public function __construct($instanceA){
      $this->propA  = $instanceA;
   }
   public function __destruct(){
      unset($this->propA);
   }
}
 
while(true){
   $a = new A();
   $a->__destruct();
   echo round(memory_get_usage(true)/1048576,2)."MB\n";
}
 
?>

Cette fois, la fuite de mémoire est colmaté. Après chaque passage dans la boucle l’objet créé est détruit par un appel explicite à la méthode __destruct.

Pour aller plus loin, je vous propose de lire cette article très complet sur le garbage collector de PHP5.3.

Articles similaires :

Mots-clefs : , , ,

Laisser une réponse