Maison > Java > javaDidacticiel > Partage d'exemples de code de fuites de mémoire ThreadLocal en Java (image)

Partage d'exemples de code de fuites de mémoire ThreadLocal en Java (image)

黄舟
Libérer: 2017-03-23 10:47:17
original
1881 Les gens l'ont consulté

Avant-propos

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.

Cas et analyse

Contexte du problème

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();
        }
}
Copier après la connexion

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ésoudre le problème

Réfléchissons-y maintenant : pourquoi la ThreadLocal sous-classe ci-dessus provoque-t-elle une fuite de mémoire ?

WebappClassLoader

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é ?

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 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 :

  1. 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.

  2. Le ClassLoader qui a chargé cette classe a été recyclé.

  3. 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.

Diagramme de référence

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 )

Partage dexemples de code de fuites de mémoire ThreadLocal en Java (image)

下面,我们根据上面的图来分析WebappClassLoader泄漏的原因。

  1. LeakingServlet持有staticMyThreadLocal,导致myThreadLocal的生命周期跟LeakingServlet类的生命周期一样长。意味着myThreadLocal不会被回收,弱引用形同虚设,所以当前线程无法通过ThreadLocal<a href="http://www.php.cn/code/8210.html" target="_blank">Map</a>的防护措施清除counter的强引用。

  2. 强引用链: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();
        }
}
Copier après la connexion

两个在 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();
        }
}
Copier après la connexion

提示

Partage dexemples de code de fuites de mémoire ThreadLocal en Java (image)

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!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal