La faute au garbage collector pourri de PHP... Enfin plutôt sa gestion des références croisées : il suffit qu'un des objets ait un attribut qui a lui-même une référence vers cet objet (note : c'est le cas le plus simple, ils peuvent être plusieurs dans la boucle) et ces références implicites sont super mal gérées.
Prenons un exemple : avec une classe "ObjetLourd" qui contient une grosse chaine de caractère, et une classe "Conteneur" qui utilise un objet lourd.
class ObjetLourd
{
public $objet = null;
public $conteneur = null;
public function __construct($conteneur )
{
$this->conteneur = $conteneur ;
$this->chaine = str_repeat('azerty', 1000);
}
}
class Conteneur
{
public $objet = null;
public function __construct()
{
$this->objet = new ObjetLourd($this);
}
}
Quelque soit le code de la classe "Conteneur", on s'attendrait à ce que le script suivant soit à mémoire constante :
while (true) {
$c = new Conteneur();
//$c = null; // le mettre ou non ne change rien vu que la variable est réutilisée
}
Raté
Allowed memory size of 134217728 bytes exhausted (tried to allocate 6001 bytes) au bout de quelques tours de boucles.
Le script sera à mémoire constante dans les deux cas suivants :

ObjetLourd n'a pas de référence à son conteneur, dans ce cas aucun souci les références sont bien nettoyées et le script est bien à mémoire constante.
class ObjetLourd
{
public $objet = null;
public function __construct()
{
$this->chaine = str_repeat('azerty', 1000);
}
}
class Conteneur
{
public $objet = null;
public function __construct()
{
$this->objet = new ObjetLourd();
}
}

L'instance de ObjetLourd n'est pas un attribut de Conteneur, mais simplement une variable du contexte de la méthode, dans ce cas la variable est supprimée et l'objet correctement détruit.
class ObjetLourd
{
public $objet = null;
public $conteneur = null;
public function __construct($conteneur )
{
$this->conteneur = $conteneur ;
$this->chaine = str_repeat('azerty', 1000);
}
}
class Conteneur
{
public function __construct()
{
$objet = new ObjetLourd($this);
}
}
En gros, ça foire dès qu'il y a des références croisées : Conteneur a un attribut qui est une référence à l'ObjetLourd, qui a lui-même une référence vers le Conteneur. C'est pourtant un cas ultra-standard, mais dans ce cas PHP ne nettoiera rien du tout, la mémoire sera perdue ad vitam aeternam.
C'est même pire que ça : rajoute un destructeur dans Conteneur pour voir (par exemple function __destruct() { echo '!'; }), tu vas être effrayé.
Dans le premier code (référence croisée), le destructeur n'est
même pas appelé ! On peut essayer d'utiliser unset() ou = null, on l'a dans le uc'

(dans les autres cas de référence non croisées, le destructeur sera bien appelé).
Seul moyen dans cet exemple de faire fonctionner la boucle avec le premier code fourni, c'est de supprimer la source de la référence croisée : $c->objet (puisque $c->objet a un attribut qui fait référence à $c, en l'annulant on supprime la référence croisée, et ça marche) :
while (true) {
$c = new Conteneur();
$c->objet = null; // suppression de la référence croisée
// là, le destructeur (et donc le nettoyage de mémoire aussi) est bien appelé
}
Voilà pour le topo, qui a priori devrait expliquer pourquoi ta mémoire n'est pas libérée. Typiquement dans DOMDocument, un noeud a une référence vers son parent, et une référence vers chacun de ses fils.
Pour conclure : ta seule chance de pouvoir gérer correctement la mémoire c'est de pouvoir supprimer toute référence à ton objet LaTeX2Xml via ses attributs (idéalement qu'il y ait un système prévu dans l'API permettant de supprimer les références croisées, comme un destructeur qui se charge de ça et qui pourrait être appelé manuellement, ou que ce soit des attributs publics). Sinon, désolé, PHP et la mémoire ça fait deux
