J'ai WebApplication qui est déployé dans Tomcat 7.0.70. J'ai simulé la situation suivante:
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.
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.
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).
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.
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?
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.
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 ThreadLocal
name__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 ThreadLocal
name__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 à ThreadLocal
name__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!
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.
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.
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.
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é.