web-dev-qa-db-fra.com

Fuite de mémoire lors du redéploiement de l'application dans Tomcat

J'ai WebApplication qui est déployé dans Tomcat 7.0.70. J'ai simulé la situation suivante:

  1. J'ai créé le vidage de tas.
  2. Ensuite, j'ai envoyé la requête HTTP et dans la méthode du service, j'ai imprimé le thread actuel et son classLoader. Et puis j'ai appelé Thread.currentThread.sleep (10000).
  3. Et au même moment, j'ai cliqué sur «Annuler le déploiement de cette application» dans la page d'administration de Tomcat.
  4. J'ai créé un nouveau vidage de tas.
  5. Après quelques minutes, j'ai créé un nouveau dump hep.


RÉSULTATS


Vidage de fil

Sur l'écran suivant, vous pouvez voir qu'après avoir cliqué sur "redéployer", tous les threads (associés à cette application Web) ont été supprimés à l'exception du thread "http-apr-8081-exec-10". Lorsque j'ai défini l'attribut de Tomcat "renewThreadsWhenStoppingContext == true", vous pouvez donc voir qu'après un certain temps ce fil ("http-apr-8081-exec-10") a été tué et qu'un nouveau fil (http-apr-8081-exec-11 ) a été créé à la place de celui-ci. Donc, je ne m'attendais pas à avoir l'ancien WCL après la création de heap dump 3, car il n'y a pas d'anciens threads ni d'objets.

 enter image description here

Heapd dump 1

Sur les deux écrans suivants, vous pouvez voir que lorsque l'application était en cours d'exécution, il n'y avait qu'un seul WCL (son paramètre "started" = true). Et le fil "http-apr-8081-exec-10" avait le contextClassLoader = URLClassLoader (car il se trouvait dans le pool de Tomcat). Je ne parle que de ce fil, car vous pourrez voir que ce fil gérera ma future requête HTTP.

 enter image description here

 enter image description here

Envoi de la requête HTTP

Maintenant, j'envoie la requête HTTP et dans mon code, je reçois des informations sur le thread actuel. Vous pouvez voir que ma requête est traitée par le thread "http-apr-8081-exec-10".

дек 23, 2016 9:28:16 AM c.c.c.f.s.r.ReportGenerationServiceImpl INFO:  request has been handled in 
   thread = http-apr-8081-exec-10,  its contextClassLoader = WebappClassLoader
   context: /hdi
   delegate: false
   repositories:
   /WEB-INF/classes/
   ----------> Parent Classloader: Java.net.URLClassLoader@4162ca06

Ensuite, je clique sur "Redéployer mon application Web" et le message suivant s'affiche dans la console.

 дек 23, 2016 9:28:27 AM org.Apache.catalina.loader.WebappClassLoaderBase clearReferencesThreads
 SEVERE: The web application [/hdi] appears to have started a thread named [http-apr-8081-exec-10] but has failed to stop it. This is very likely to create a memory leak.

Heapd dump 2

Sur les écrans suivants, vous pouvez voir qu'il existe deux instances WebAppClassLoader. L'un d'eux (numéro 1) est ancien (son attribut "démarré" = faux). Et le WCL # 2 a été créé après le redéploiement de l'application (son attribut "démarré" = vrai). Et le fil de discussion que nous examinons a contextClassLoader = "org.Apache.catalina.loader.WebappClassLoader". Pourquoi? Je m'attendais à voir contextClassLoader = "Java.net.URLClassLoader" (après tout, lorsqu'un thread termine son travail, il est renvoyé au pool De Tomcat et son attribut "contextClassLoader" est défini sur tout chargeur de classe de base).

 enter image description here

 enter image description here

 enter image description here

Heapd dump 3

Vous pouvez voir qu'il n'y a pas de fil "http-apr-8081-exec-10", mais qu'il existe un fil "http-apr-8081-exec-11" et que contextClassLoader = "WebappClassLoader" ( Pourquoi pas URLClassLoader?).

En fin de compte, il y a ce qui suit: il y a le fil "http-apr-8081-exec-11" qui a la référence au WebappClassLoader # 1. Et bien évidemment, lorsque je crée la "Racine de GC la plus proche" sur la WCL # 1, je vois la référence du fil 11.

 enter image description here

 enter image description here

Questions.

Comment puis-je dire de force à Tomcat de renvoyer l'ancienne valeur contextClassLoader (URLClassLoader) après que le thread a terminé son travail?

Comment puis-je m'assurer que Tomcat ne copie pas l'ancienne valeur "contextClassLoader" lors du renouvellement du thread?

Peut-être connaissez-vous un autre moyen de résoudre mon problème?

21
Konstantin B.

Tomcat n'est généralement pas une bonne option dans les environnements de production. J'utilisais Tomcat sur quelques applications de production et j'ai constaté que même si la taille du segment de mémoire et d'autres configurations étaient correctement configurées, la consommation de mémoire augmente à chaque fois que vous rechargez votre application. Tant que vous n'avez pas redémarré le service Tomcat, la mémoire n'est pas complètement récupérée. Nous avons testé toutes ces expériences telles que l'effacement des journaux, le redéploiement de toutes les applications, le redémarrage régulier de Tomcat une fois par mois ou par semaine pendant les heures les moins occupées. Mais à la fin, je dois dire que nous avons déplacé nos environnements de production vers Glassfish et WebSphere. 

J'espère que vous auriez déjà parcouru ces pages:

Fuite de mémoire dans une application Web Java

Tomcat réparer une fuite de mémoire?

https://developers.redhat.com/blog/2014/08/14/find-fix-memory-leaks-Java-application/

http://www.tomcatexpert.com/blog/2010/04/06/tomcats-new-memory-leak-prevention-and-detection

Si vos applications Web ne sont pas étroitement associées à Tomcat, vous pouvez envisager d'utiliser un autre conteneur Web. Maintenant, nous utilisons le Glassfish, même sur les machines de développement et la production, et le jour où nous prenons cette décision, nous économisons beaucoup de temps. Bien que Glassfish et d'autres serveurs de ce type prennent plus de temps, ils ne sont pas aussi légers que le Tomcat, mais après la vie est un peu plus facile.

3
Naveed Kamran

D'après mon expérience de ce problème, ce qui empêchait Tomcat de gérer correctement les anciens chargeurs de classes était quelques ThreadLocalname__s que deux ou trois frameworks que j'utilisais créaient (et ne traitaient pas correctement).

Quelque chose de semblable à ce qui est expliqué ici: ThreadLocal & Memory Leak

J'ai essayé de finaliser correctement cette ThreadLocalname__s et ma fuite a réduit BEAUCOUP. Il y avait encore des fuites, mais je pouvais gérer 10 fois plus de redéploiements qu'auparavant.

Je vérifierais certainement vos sauvegardes de mémoire sur des objets pouvant être connectés d'une manière ou d'une autre à ThreadLocalname__s (ils sont très courants, spécialement si vous utilisez quelque chose pour contrôler des transactions ou tout ce qui est isolé par thread.

J'espère que ça aide!

1
BrunoJCM

Tomcat n'est généralement pas une bonne option dans les environnements de production. J'utilisais Tomcat sur quelques applications de production et j'ai constaté que même si la taille du segment de mémoire et d'autres configurations étaient correctement configurées, la consommation de mémoire augmente à chaque fois que vous rechargez votre application. Tant que vous n'avez pas redémarré le service Tomcat, la mémoire n'est pas complètement récupérée. Nous avons testé toutes ces expériences telles que l'effacement des journaux, le redéploiement de toutes les applications, le redémarrage régulier de Tomcat une fois par mois ou par semaine pendant les heures les moins occupées. Mais à la fin, je dois dire que nous avons déplacé nos environnements de production vers Glassfish et WebSphere.

0
Wael Mofreh

Une fuite de mémoire dans le redéploiement de Tomcat est un problème très ancien. Le seul moyen de le résoudre consiste à redémarrer Tomcat au lieu de redéployer l'application. Si vous avez plusieurs applications, vous devez exécuter plusieurs services Tomcat sur des ports différents et les associer à nginx.

0
A.Alexander

Recherchez les utilisations de ThreadLocal qui empêchent votre ClassLoader d'être nettoyé. Supprimez les références à vos classes dans les valeurs ThreadLocal ou utilisez https://github.com/codesinthedark/ImprovedThreadLocal au lieu de ThreadLocal.

0
CodesInTheDark

Des centaines d'instances Tomcat s'exécutant dans plusieurs environnements (également de production), la seule solution raisonnable à ce problème consiste à arrêter et à redémarrer chaque Tomcat à une heure définie quotidiennement (la nuit).

Nous avons essayé beaucoup d’astuces, mais c’est la solution durable à nos besoins de disponibilité.

0
Marc.S