fuite mémoire Axis avec Tomcat
L’erreur est une mauvaise manipulation des ThreadLocals du langage Java, objet introduit en version 2 du langage.
1. Rappel sur Java, les Threads, et les ThreadLocals
Le langage Java, depuis sa version 2, intègre dans son API de référence un objet un peu particulier, dénommé ThreadLocal, qui permet d’obtenir une variable locale à chaque Thread qui utilise la classe. Dans le cas courant, les variables d’une classe sont accessible par chacun des Thread qui la manipule.
C’est un comportement qui n’est pas souhaitable dans tous les cas. Cette classe est très pratique sur les données que l’on ne souhaite pas synchroniser. Ces variables sont déclarées statiques et sont instanciées en même temps que le Thread. Seul ce dernier a accès à cette variable.
De la même façon, la variable ne peut être libérée de la mémoire par le GC1 qu’à la libération du Thread.
2. Explications sur l’anomalie rencontrée
L’anomalie vient d’une interférence entre Tomcat et le composant Axis. Axis utilise des ThreadLocal pour stocker certaines de ses variables. Dans la plupart des cas, ça ne pose pas de problèmes particuliers. Dans Axis 1.4, par exemple, il subsiste encore quelques ThreadLocal :- src/org/apache/axis/client/AdminClient.java => EngineConfiguration
- src/org/apache/axis/client/HappyClient.java => Sert juste à obtenir la version de Java
- src/org/apache/axis/client/ServiceFactory.java => EngineConfiguration
- src/org/apache/axis/utils/cache/MethodCache.java => Map
- src/org/apache/axis/utils/XMLUtils.java => DocumentBuilder
Mais ces derniers n’interfèrent pas avec Tomcat. Par contre, la classe Service, qui est utilisée pour répondre aux web services (SOAP) est rendue persistente par Tomcat, en utilisant un Thread.
Tomcat utilise des Threads pour répondre à beaucoup de requêtes en simultanées. Il utilise notamment la classe Stub pour répondre aux requêtes rpc (SOAP).
Le problème qui se pose est le fait que Tomcat utilise un pool de Thread. Le Thread n’est donc jamais libéré. L’objet Call dont il est question, qui stocke la requête, est stocké dans un ThreadLocal par un objet dénommé Service, invoqué par le Stub. Il est ainsi devenu persistent entre 2 requêtes faites au web service. De la même façon, l’objet Call est persistent pour les autres applications hébergées : le Thread qui va les exécuter aura toujours cet objet Call.
Enfin, à chaque fois que Tomcat réinstancie le Stub dans un Thread, il réinstance un nouvel objet Service, et donc un nouveau ThreadLocal, propre à cette classe. Quand le Stub est libéré, le Service est libéré et ce nouveau ThreadLocal est encore présent.
3. Qu’est-ce que fait le correctif ?
Il corrige l’interférence en évitant l’utilisation du ThreadLocal. Cette variable est là pour obtenir la dernière requête appelée. Il n’était nulle besoin de la protéger en la rendant locale à un Thread, car c’est un objet propre au Thread, qui n’est pas partagé : il y a un Thread par Service.
C’est aussi le correctif que nous avons retrouvé dans le code différentiel entre la 1.2 et la 1.2.1. La 1.2.1 étant la version qui intègre le correctif officiel de la communauté, toujours présent en 1.4.
Le correctif passe donc cette variable en variable local, au lieu d’une variable en ThreadLocal. Ca permet de recréer un nouvel Objet Call à chaque fois sans conserver en mémoire la référence sur l’ancien. Il peut donc être nettoyé par le GC. Le prix à payer est le fait que la classe n’est plus ThreadSafe. On n’a donc plus la garantie que l’objet Call est effectivement le dernier appelé.
Pour conserver cette garantie, ils ont déplacé l’objet dans la classe Stub (/src/org/apache/axis/client/Stub.java:410) en version 1.2.1. C’est indiqué dans le commentaire du patch fourni. Nous pouvons bien évidemment vous fournir un correctif plus complet, qui backporte cette partie de l’API.
4. Une autre solution ?
On pouvait aussi mettre à jour l’objet Call du ThreadLocal, au lieu d’en recréer un nouveau à chaque fois. C’est de cette manière qu’il faut utiliser les ThreadLocal pour qu’ils fonctionnent correctement.
Ce correctif est un backport du correctif appliqué en version 1.2.1, c’est pourquoi il n’a pas été reversé.
1 Garbage Collector, le module qui libère la mémoire dans la machine virtuelle java.
Link :This contribution has been submitted on 2007-02-16 by Michel LOISELEUR and its has been approved on 2007-02-20 .
This patch has been made for version 1.1