Cas et analyse
Problème Contexte
Dans Tomcat, le code suivant est tout à l'intérieur du WebApp, ce qui fera la fuite de téléchargement WebappClassLoader et ne peut pas être recyclé.
classe publique MyCounter {private int count = 0; public void incrément () {count ++; } public int getCount () {return count; }} classe publique MyThreadLocal étend ThreadLocal <myCounter> {} classe publique LakingServlet étend httpservlet {private static myThreadlocal myThreadlocal = new myThreadLocal (); Protected void doGet (HttpServLetRequest Request, HttpServletResponse Response) lève ServletException, ioException {myCounter Counter = MyThreadLocal.get (); if (counter == null) {compter = new myCounter (); myThreadLocal.set (compteur); } réponse.getWriter (). println ("Le thread actuel a servi ce servlet" + compteur.getCount () + "Times"); Counter.increment (); }} Dans le code ci-dessus, tant que LeakingServlet est appelée une fois et que l'exécution du thread n'est pas arrêtée, WebappClassLoader se produira. Chaque fois que vous reload l'application, vous aurez une instance WebappClassLoader supplémentaire, ce qui mènera éventuellement à PermGen OutOfMemoryException .
Résoudre le problème
Réfléchissons maintenant: pourquoi ThreadLocal ci-dessus provoque-t-elle des fuites de mémoire?
WebAppClassLoader
Tout d'abord, nous devons comprendre ce qu'est le WebappClassLoader ?
Pour les applications Web exécutées dans des conteneurs Java EE, la mise en œuvre des chargeurs de classe est différente de celle des applications Java générales. Différents conteneurs Web seront également mis en œuvre différemment. Dans Apache Tomcat, chaque application Web a une instance de chargeur de classe correspondante. Ce chargeur de classe utilise également le mode proxy, la différence est qu'il essaie d'abord de charger une certaine classe, puis proxy au chargeur de classe parent s'il ne peut être trouvé. C'est l'opposé de l'ordre des chargeurs de classe généraux. Il s'agit d'une pratique recommandée dans la spécification du servlet Java, et son objectif est de faire en sorte que les classes de l'application Web privilégient plus haut que celles fournies par le conteneur Web. Une exception à ce modèle de proxy est que les classes de la bibliothèque Java Core ne sont pas dans le cadre de la recherche. Il s'agit également d'assurer la sécurité du type de la bibliothèque Java Core.
En d'autres termes, WebappClassLoader est un chargeur de classe personnalisé pour TomCat pour charger les WebApps. Le chargeur de classe de chaque WebApp est différent. Il s'agit d'isoler les classes chargées de différentes applications.
Alors, quelles fonctionnalités de WebappClassLoader ont à voir avec les fuites de mémoire? Il n'est pas encore visible, mais c'est une fonctionnalité très importante qui mérite notre attention: chaque WebApp a son propre WebappClassLoader , qui est différent du chargeur de classe Java Core.
Nous savons que WebappClassLoader doit être parce qu'elle est fortement référencée par d'autres objets, nous pouvons donc essayer de dessiner leur diagramme de relation de référence. etc! Que fonctionne exactement un chargeur de classe? Pourquoi est-il obligé de citer?
Cycle de vie de classe et chargeur de classe
Pour résoudre le problème ci-dessus, nous devons étudier la relation entre le cycle de vie de la classe et le chargeur de classe.
La principale chose liée à notre cas est la désinstallation de classe:
Une fois le cours utilisé, si la situation suivante est remplie, la classe sera désinstallée:
1. Toutes les instances de cette classe ont été recyclées, c'est-à-dire qu'aucun cas de cette classe n'existe dans le tas Java.
2. ClassLoader chargement de cette classe a été recyclé.
3. L'objet java.lang.Class correspondant à cette classe n'est référencé nulle part, et il n'y a pas de méthode pour accéder à la classe par la réflexion nulle part.
Si toutes les trois conditions ci-dessus sont remplies, le JVM désinstallera la classe pendant la collecte des ordures dans la zone de la méthode. Le processus de désinstallation de la classe vise en fait à effacer les informations de classe dans le domaine de la méthode, et tout le cycle de vie de la classe Java se termine.
Les classes chargées par le chargeur de classe accompagnée de la machine virtuelle Java ne seront jamais désinstallées pendant le cycle de vie de la machine virtuelle. Les chargeurs de classe fournis avec les machines virtuelles Java incluent les chargeurs de classe racine, les chargeurs de classe d'extension et les chargeurs de classe système. La machine virtuelle Java lui-même fait toujours référence à ces chargeurs de classe, et ces chargeurs de classe se réfèrent toujours aux objets de classe de la classe qu'ils chargent, de sorte que ces objets de classe sont toujours accessibles.
Les classes chargées par des chargeurs de classe définies par l'utilisateur peuvent être déchargées.
Notez la phrase ci-dessus. Si WebappClassLoader fuit, cela signifie que les classes qu'il charge ne peut pas être désinstallée, ce qui explique pourquoi le code ci-dessus provoque PermGen OutOfMemoryException .
Regardez l'image ci-dessous au point clé
Nous pouvons constater que l'objet de chargeur de classe est associé bidirectionnellement à l'objet de classe qu'il charge. Cela signifie que l'objet de classe peut être le coupable de la référence forcée à WebappClassLoader , la faisant fuiter.
Citer le diagramme des relations
Après avoir compris la relation entre le chargeur de classe et le cycle de vie d'une classe, nous pouvons commencer à dessiner un diagramme de relation de référence. ( LeakingServlet.class et myThreadLocal sur la figure ne sont pas rigoureux dans le dessin de référence, principalement pour exprimer que myThreadLocal est une variable de classe)
Ci-dessous, nous analysons la cause de la fuite de chargeur WebappClassLoader basée sur la figure ci-dessus.
1. LeakingServlet tient MyThreadLocal static , entraînant le cycle de vie de myThreadLocal aussi longtemps que le cycle de vie de LeakingServlet . Cela signifie que myThreadLocal ne sera pas recyclé et que les références faibles sont inutiles, donc le thread actuel ne peut pas effacer la forte référence du compteur grâce aux mesures de protection de ThreadLocalMap .
2. Chaîne de référence forte: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , provoquant la fuite de téléchargement WebappClassLoader .
Résumer
Les fuites de mémoire sont difficiles à détecter et sont souvent causées par de nombreuses raisons. Le threadlocal est devenu un visiteur fréquent des fuites de mémoire en raison de son cycle de vie lié aux fils, et un peu de négligence entraînera une catastrophe. Cet article n'est qu'une analyse d'un cas spécifique. Si vous pouvez l'utiliser pour l'appliquer à d'autres cas, ce serait excellent. J'espère que cet article sera utile à tout le monde.