Memory Leak du client CXF

Les tests de charge d’une nouvelle fonctionnalité m’a récemment permis de détecter un comportement inattendu de CXF s’apparentant à une fuite mémoire. Fusion de Celtix et de XFire, le framework CXF propose une implémentation cliente et serveur de web services SOAP et REST. Le comportement suspect concerne la partie cliente d’un web service SOAP avec pièce-jointes.

Les symptômes ont été observés dans les conditions suivantes. Un tir de charge avec JMeter simule l’upload de fichiers de 4 Mo. Trente utilisateurs connectés simultanément uploadent des fichiers PDF. D’une durée de 5mn, le scénario fonctionnel mettant en jeu l’upload de fichiers est réitéré pendant 3h. A l’issu du tir, aucune erreur technique ou fonctionnelle n’est remontée. Par contre, l’analyse de l’empreinte mémoire est suspecte : non seulement cette nouvelle fonctionnalité a nécessité davantage de mémoire que lors des tirs précédents, mais surtout : la mémoire n’est jamais libérée, même après l’expiration des sessions utilisateurs.

2014-02-cxf-attachments-memory-leak-2

Origine du problème

Un Heap Dump de la JVM a permis de déterminer le type d’objets résidents en mémoire et également de quelles classes ces objets ont été alloués. Dans mon cas, près de 300 Mo de tableaux de bytes occupaient la Heap. La plupart de ces tableaux occupaient 5 Mo.
La pile d’appel ci-dessous montre que ces tableaux sont créés par le client CXF, et plus précisément par la classe AttachmentSerializer chargée de sérialiser en XML le SoapMessage émis par le client CXF.

2014-02-cxf-attachments-memory-leak-5Reproductible sur un poste de développement, le debugger d’Eclipse permet de diagnostique que les 5 Mo de tableau de bytes correspondent au message SOAP et sa pièce-jointe encodée en base 64 :

2014-02-cxf-attachments-memory-leak-7En interne, la classe ClientImpl de CXF maintient une Map requestcontext  associant un thread à un message SOAP.  Les derniers messages émis par le client CXF sont stockés dans cette Map. Au final, plus le nombre de threads faisant appel au client CXF est élevé, plus CXF demandera de mémoire.

Correction

La correction mise en œuvre a été d’implémenter un intercepteur CXF chargé de déréférencer l’instance de AttachmentSerializer une fois le message SOAP envoyé. Le Garbage Collector peut alors libérer la mémoire. L’intercepteur est positionné sur la toute dernière phase CXF : SETUP_ENDING.

Extrait de la classe ClearAttachmentsOutInterceptor.java

Lors de la déclaration Spring d’un client CXF, l’intercepteur ClearAttachmentsOutInterceptor doit être positionné dans la balise <jaxws:outInterceptors>  :

<jaxws:client id="myWebServiceClient" serviceClass="MyWebService"
    address="http://localhost:8080/ws/MyWebService">
  <jaxws:outInterceptors>
    <bean class="ClearAttachmentsOutInterceptor"/>
  </jaxws:outInterceptors>
</jaxws:client>

Conclusion

Surpris de découvrir une telle problématique de mémoire dans un framework aussi populaire,  je ne comprends pas le choix pris par les développeurs de CXF de conserver trace d’un message une fois celui-ci émis et sa réponse reçue. Une hypothèse : éviter de réallouer une grappe d’objets à chaque appel de web service ?
Chose rassurante, je ne suis pas le seul à avoir rencontré ces soucis de mémoire lors de tirs de charge. Ce problème est en effet décrit sur les Jira des ESB Apache Service Mix (SMXCOMP-855) et Mule ESB (MULE-6434). Chacun d’eux a apporté sa propre solution de contournement.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.