J'ai déjà écrit une analyse approfondie du problème de fuite de mémoire ThreadLocal, qui est une analyse théorique du <span class="wp_keywordlink">ThreadLocal</span>
problème de fuite de mémoire. Dans cet article, nous analyserons la fuite de mémoire réelle. cas. Le processus d'analyse du problème est plus important que le résultat. Ce n'est qu'en combinant la théorie et la pratique que la cause de la fuite de mémoire peut être analysée en profondeur.
Dans Tomcat, les codes suivants sont tous dans l'application Web, ce qui provoquera WebappClassLoader
des fuites et ne pourra pas être recyclé.
public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class MyThreadLocal extends ThreadLocal<MyCounter> { } public class LeakingServlet extends HttpServlet { private static MyThreadLocal myThreadLocal = new MyThreadLocal(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyCounter counter = myThreadLocal.get(); if (counter == null) { counter = new MyCounter(); myThreadLocal.set(counter); } response.getWriter().println( "The current thread served this servlet " + counter.getCount() + " times"); counter.increment(); } }
Dans le code ci-dessus, tant que LeakingServlet
est appelé une fois et que le thread qui l'exécute n'est pas arrêté, WebappClassLoader
fuira. Chaque fois que vous rechargez l'application, il y aura une instance supplémentaire de WebappClassLoader
, aboutissant finalement à PermGen OutOfMemoryException
.
Réfléchissons-y maintenant : pourquoi la ThreadLocal
sous-classe ci-dessus provoque-t-elle une fuite de mémoire ?
Tout d'abord, nous devons comprendre ce que c'est WebappClassLoader
?
Pour les applications Web exécutées dans des conteneurs Java EE, le chargeur de classe est implémenté différemment des applications Java générales. Différents conteneurs Web sont implémentés différemment. Dans le cas d'Apache Tomcat, chaque application Web possède 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 s'il ne la trouve pas, il effectue ensuite un proxy vers le chargeur de classe parent. C'est l'opposé de l'ordre normal du chargeur de classe. Il s'agit d'une pratique recommandée dans la spécification Java Servlet, et son objectif est de donner la priorité aux classes propres à l'application Web par rapport aux classes fournies par le conteneur Web. Une exception à ce modèle de proxy est que les classes de la bibliothèque principale Java ne sont pas incluses dans la recherche. Il s'agit également de garantir la sécurité des types de la bibliothèque principale Java.
En d'autres termes, WebappClassLoader
est un chargeur de classe personnalisé utilisé par Tomcat pour charger des applications Web. Le chargeur de classe de chaque application Web est différent. Il s'agit d'isoler les classes chargées par différentes applications.
Alors, qu'est-ce que la fonctionnalité de WebappClassLoader
a à voir avec les fuites de mémoire ? Ce n'est pas encore visible, mais il a une fonctionnalité très importante qui mérite notre attention : chaque webapp aura son propre WebappClassLoader
, qui est différent du chargeur de classe principal Java.
Nous savons que la fuite de WebappClassLoader
doit être due au fait qu'il est fortement référencé par d'autres objets, alors nous pouvons essayer de dessiner leur diagramme de relation de référence. etc! Quel est exactement le rôle d’un chargeur de classe ? Pourquoi est-il fortement cité ?
Pour résoudre le problème ci-dessus, nous devons étudier la relation entre le cycle de vie de classe et le chargeur de classe.
L'essentiel lié à notre cas est la désinstallation des classes :
Après l'utilisation de la classe, si les conditions suivantes sont remplies, la classe sera désinstallée :
Toutes les instances de cette classe ont été recyclées, c'est-à-dire qu'il n'y a aucune instance de cette classe dans le tas Java.
Le ClassLoader
qui a chargé cette classe a été recyclé.
L'objet java.<a href="http://www.php.cn/java/java-ymp-Lang.html" target="_blank">lang</a>.Class
correspondant de cette classe n'est référencé nulle part, et il n'existe aucune méthode pour accéder à cette classe par réflexion nulle part.
Si les trois conditions ci-dessus sont remplies, la JVM désinstallera la classe lors du garbage collection dans la zone des méthodes. Le processus de désinstallation de la classe consiste en fait à effacer les informations de classe dans la zone. zone de méthode.Java Le cycle de vie complet de la classe est terminé.
Les classes chargées par le chargeur de classes fourni avec la machine virtuelle Java ne seront jamais déchargées pendant le cycle de vie de la machine virtuelle. Les chargeurs de classes fournis avec la machine virtuelle Java incluent les chargeurs de classes racine, les chargeurs de classes d'extension et les chargeurs de classes système. La machine virtuelle Java elle-même fera toujours référence à ces chargeurs de classes, et ces chargeurs de classes feront toujours référence aux objets Class des classes qu'ils chargent, de sorte que ces objets Class soient toujours accessibles.
Les classes chargées par des chargeurs de classes définis par l'utilisateur peuvent être déchargées.
Faites attention à la phrase ci-dessus, si WebappClassLoader
est divulgué, cela signifie que les classes qu'il charge ne peuvent pas être déchargées, ce qui explique pourquoi le code ci-dessus provoquera PermGen OutOfMemoryException
.
Points clés regardez l'image ci-dessous
Nous pouvons constater que l'objet chargeur de classe est lié de manière bidirectionnelle à l'objet classe qu'il charge. Cela signifie que l'objet Class peut être la référence forte WebappClassLoader
, provoquant sa fuite.
Après avoir compris la relation entre le chargeur de classe et le cycle de vie de la classe, nous pouvons commencer à dessiner le diagramme de référence. (Les références de LeakingServlet.class
et myThreadLocal
dans l'image ne sont pas précises, principalement pour exprimer que myThreadLocal
est une classe variable )
下面,我们根据上面的图来分析WebappClassLoader
泄漏的原因。
LeakingServlet
持有static
的MyThreadLocal
,导致myThreadLocal
的生命周期跟LeakingServlet
类的生命周期一样长。意味着myThreadLocal
不会被回收,弱引用形同虚设,所以当前线程无法通过ThreadLocal<a href="http://www.php.cn/code/8210.html" target="_blank">Map</a>
的防护措施清除counter
的强引用。
强引用链:thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader
,导致WebappClassLoader
泄漏。
内存泄漏是很难发现的问题,往往由于多方面原因造成。ThreadLocal
由于它与线程绑定的生命周期成为了内存泄漏的常客,稍有不慎就酿成大祸。
本文只是对一个特定案例的分析,若能以此举一反三,那便是极好的。最后我留另一个类似的案例供读者分析。
假设我们有一个定义在 Tomcat Common Classpath 下的类(例如说在 tomcat/lib
目录下)
public class ThreadScopedHolder { private final static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(); public static void saveInHolder(Object o) { threadLocal.set(o); } public static Object getFromHolder() { return threadLocal.get(); } }
两个在 webapp 的类:
public class MyCounter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class LeakingServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { MyCounter counter = (MyCounter)ThreadScopedHolder.getFromHolder(); if (counter == null) { counter = new MyCounter(); ThreadScopedHolder.saveInHolder(counter); } response.getWriter().println( "The current thread served this servlet " + counter.getCount() + " times"); counter.increment(); } }
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!