Question

Quelqu'un a-t-il un exemple comment procéder? Sont-ils manipulés par le collecteur des ordures? J'utilise Tomcat 6.

Était-ce utile?

La solution

Le Javadoc dit ceci:

"Chaque thread contient une référence implicite à sa copie d'une variable de thread-locale tant que le thread est vivant et que l'instance threadlocal est accessible; après qu'un thread disparaît, toutes ses copies des instances de filetage sont soumises à la collecte des ordures (sauf si d'autres références à ces copies existent).

Si votre application ou (si vous parlez de threads de demande) utilise un pool de threads, ce qui signifie que les threads ne meurent pas. Si nécessaire, vous devrez traiter vous-même les habitants des fils. La seule façon propre de le faire est d'appeler le ThreadLocal.remove() méthode.

Il y a deux raisons pour lesquelles vous voudrez peut-être nettoyer les locaux de fil pour les threads dans une piscine de fil:

  • Pour éviter les fuites de mémoire (ou de ressources hypothétiquement), ou
  • pour éviter une fuite accidentelle d'informations d'une demande à une autre via les habitants du thread.

Les fuites de mémoire locales de thread ne devraient normalement pas être un problème majeur avec les pools de threads bornés, car les habitants des fils sont susceptibles d'être écrasés finalement; c'est-à-dire lorsque le fil est réutilisé. Cependant, si vous faites l'erreur de créer un nouveau ThreadLocal instances encore et encore (au lieu d'utiliser un static variable pour contenir une instance singleton), les valeurs locales de thread ne seront pas écrasées et s'accumuleront dans chaque thread threadlocals carte. Cela pourrait entraîner une fuite sérieuse.


En supposant que vous parlez des locaux de threads qui sont créés / utilisés lors du traitement par WebApp d'une demande HTTP, alors une façon d'éviter les fuites locales de thread est d'enregistrer un ServletRequestListener avec votre webapp ServletContext et implémenter requestDestroyed Méthode pour nettoyer les locaux du fil pour le thread actuel.

Notez que dans ce contexte, vous devez également considérer la possibilité de informations fuir d'une demande à une autre.

Autres conseils

Voici un code pour nettoyer toutes les variables locales du thread à partir du thread actuel lorsque vous n'avez pas de référence à la variable locale du thread réel. Vous pouvez également le généraliser aux variables locales de fil de nettoyage pour d'autres threads:

    private void cleanThreadLocals() {
        try {
            // Get a reference to the thread locals table of the current thread
            Thread thread = Thread.currentThread();
            Field threadLocalsField = Thread.class.getDeclaredField("threadLocals");
            threadLocalsField.setAccessible(true);
            Object threadLocalTable = threadLocalsField.get(thread);

            // Get a reference to the array holding the thread local variables inside the
            // ThreadLocalMap of the current thread
            Class threadLocalMapClass = Class.forName("java.lang.ThreadLocal$ThreadLocalMap");
            Field tableField = threadLocalMapClass.getDeclaredField("table");
            tableField.setAccessible(true);
            Object table = tableField.get(threadLocalTable);

            // The key to the ThreadLocalMap is a WeakReference object. The referent field of this object
            // is a reference to the actual ThreadLocal variable
            Field referentField = Reference.class.getDeclaredField("referent");
            referentField.setAccessible(true);

            for (int i=0; i < Array.getLength(table); i++) {
                // Each entry in the table array of ThreadLocalMap is an Entry object
                // representing the thread local reference and its value
                Object entry = Array.get(table, i);
                if (entry != null) {
                    // Get a reference to the thread local object and remove it from the table
                    ThreadLocal threadLocal = (ThreadLocal)referentField.get(entry);
                    threadLocal.remove();
                }
            }
        } catch(Exception e) {
            // We will tolerate an exception here and just log it
            throw new IllegalStateException(e);
        }
    }

Il n'y a aucun moyen de nettoyer ThreadLocal valeurs sauf de l'intérieur le fil qui les a mis là-dedans en premier lieu (ou lorsque le fil est collecté aux ordures - pas le cas avec les fils de travail). Cela signifie que vous devez prendre soin de nettoyer vos threadlocal lorsqu'une demande de servlet est terminée (ou avant de transférer AsyncConText vers un autre fil du servlet 3), car après ce point, vous n'aurez peut-être jamais la possibilité de saisir ce fil de travailleur spécifique, et donc, Fermera la mémoire dans des situations lorsque votre application Web n'est pas exploitée pendant que le serveur n'est pas redémarré.

Un bon endroit pour faire un tel nettoyage est ServletRequestListener.RequestDestRoyed ().

Si vous utilisez le ressort, tout le câblage nécessaire est déjà en place, vous pouvez simplement mettre des choses dans votre portée de demande sans vous soucier de les nettoyer (cela se produit automatiquement):

RequestContextHolder.getRequestAttributes().setAttribute("myAttr", myAttr, RequestAttributes.SCOPE_REQUEST);
. . .
RequestContextHolder.getRequestAttributes().getAttribute("myAttr", RequestAttributes.SCOPE_REQUEST);

Lire à nouveau la documentation Javadoc soigneusement:

«Chaque thread contient une référence implicite à sa copie d'une variable de thread-locale tant que le thread est vivant et que l'instance threadlocal est accessible; Une fois qu'un fil disparaît, toutes ses copies des instances de fil locales sont soumises à la collecte des ordures (à moins que d'autres références à ces copies existent). '

Il n'est pas nécessaire de nettoyer quoi que ce soit, il y a une «condition» pour la fuite pour survivre. Ainsi, même dans un conteneur Web où le thread survit à l'application, tant que la classe WebApp est déchargée (seule référence dans une classe statique chargée dans le chargeur de classe parent empêcherait cela et cela n'a rien à voir avec des problèmes threadlocaux mais généraux avec Bocaux partagés avec des données statiques) puis la deuxième étape de la condition et la condition n'est plus remplie, donc la copie locale de fil est éligible à la collecte des ordures.

Thread Local ne peut pas être la cause des fuites de mémoire, en ce qui concerne la mise en œuvre de la documentation.

Je voudrais contribuer ma réponse à cette question même si elle est ancienne. J'avais été en proie au même problème (GSON ThreadLocal ne se faisait pas supprimer du thread de demande), et j'avais même mis à l'aise le redémarrage du serveur à chaque fois qu'il manquait de mémoire (ce qui suce beaucoup de temps !!).

Dans le contexte d'une application Web Java qui est définie sur le mode Dev (en ce que le serveur est défini pour rebondir chaque fois qu'il sent un changement dans le code, et peut-être aussi l'exécution en mode de débogage), j'ai rapidement appris que les filetages peuvent être géniaux et parfois être une douleur. J'utilisais une invocation threadlocal pour chaque demande. À l'intérieur de l'invocation. J'utiliserais parfois aussi GSON pour générer ma réponse. J'enroulerais l'invocation à l'intérieur d'un bloc «essayez» dans le filtre et le détruirais dans un bloc «enfin».

Ce que j'ai observé (je n'ai pas de métriques pour sauvegarder cela pour l'instant), c'est que si je modifiais les modifications à plusieurs fichiers et que le serveur rebondissait constamment entre mes modifications, je serais impatient et redémarrerais le serveur (Tomcat pour être précis) de l'IDE. Très probablement qu'autrement, je me retrouverais avec une exception «hors mémoire».

La façon dont je me suis déroulé était d'inclure une implémentation servletRequestListener dans mon application, et mon problème a disparu. Je pense que ce qui se passait, c'est qu'au milieu d'une demande, si le serveur rebondissait plusieurs fois, mes threadlocals ne se sont pas effacés (GSON inclus), donc j'obtiendrais cet avertissement sur les threadlocals et deux ou trois avertissements plus tard, Le serveur se bloquerait. Avec le servletResponseListener fermant explicitement mes threadlocals, le problème GSON a disparu.

J'espère que cela a du sens et vous donne une idée de la façon de surmonter les problèmes threadlocaux. Fermez-les toujours autour de leur point d'utilisation. Dans le servletRequestListener, testez chaque wrapper threadlocal, et s'il a toujours une référence valide à un objet, détruisez-le à ce moment-là.

Je dois également souligner que ce qui en fait une habitude d'envelopper un threadlocal comme variable statique à l'intérieur d'une classe. De cette façon, vous pouvez être garanti qu'en le détruisant dans le servelTequestListener, vous n'aurez pas à vous soucier des autres cas de la même classe qui traînent.

Le JVM nettoierait automatiquement tous les objets sans référence qui se trouvent dans l'objet ThreadLocal.

Une autre façon de nettoyer ces objets (disons par exemple, ces objets pourraient être tous les objets de fil non sûrs qui existent autour) est de les mettre dans une classe de support d'objet, qui le maintient essentiellement et vous pouvez remplacer la méthode Finalise pour nettoyer l'objet qui y résident. Encore une fois, cela dépend du collecteur des ordures et de ses politiques, lorsqu'il invoquera le finalize méthode.

Voici un exemple de code:

public class MyObjectHolder {

    private MyObject myObject;

    public MyObjectHolder(MyObject myObj) {
        myObject = myObj;
    }

    public MyObject getMyObject() {
        return myObject;
    }

    protected void finalize() throws Throwable {
        myObject.cleanItUp();
    }
}

public class SomeOtherClass {
    static ThreadLocal<MyObjectHolder> threadLocal = new ThreadLocal<MyObjectHolder>();
    .
    .
    .
}

La réponse de @ Lyaffe est la meilleure possible pour Java 6. Il y a quelques problèmes que cette réponse résout en utilisant ce qui est disponible dans Java 8.

La réponse de Lyaffe a été écrite pour Java 6 avant MethodHandle est devenu disponible. Il souffre de pénalités de performance en raison de la réflexion. S'il est utilisé comme ci-dessous, MethodHandle proposer Zero Overhead Accès aux champs et méthodes.

La réponse de @ Lyaffe passe également par le ThreadLocalMap.table explicitement et est sujet aux bogues. Il y a une méthode ThreadLocalMap.expungeStaleEntries() Maintenant disponible, cela fait la même chose.

Le code ci-dessous dispose de 3 méthodes d'initialisation pour minimiser le coût d'appel expungeStaleEntries().

private static final MethodHandle        s_getThreadLocals     = initThreadLocals();
private static final MethodHandle        s_expungeStaleEntries = initExpungeStaleEntries();
private static final ThreadLocal<Object> s_threadLocals        = ThreadLocal.withInitial(() -> getThreadLocals());

public static void expungeThreadLocalMap()
{
   Object threadLocals;

   threadLocals = s_threadLocals.get();

   try
   {
      s_expungeStaleEntries.invoke(threadLocals);
   }
   catch (Throwable e)
   {
      throw new IllegalStateException(e);
   }
}

private static Object getThreadLocals()
{
   ThreadLocal<Object> local;
   Object result;
   Thread thread;

   local = new ThreadLocal<>();

   local.set(local);   // Force ThreadLocal to initialize Thread.threadLocals

   thread = Thread.currentThread();

   try
   {
      result = s_getThreadLocals.invoke(thread);
   }
   catch (Throwable e)
   {
      throw new IllegalStateException(e);
   }

   return(result);
}

private static MethodHandle initThreadLocals()
{
   MethodHandle result;
   Field field;

   try
   {
      field = Thread.class.getDeclaredField("threadLocals");

      field.setAccessible(true);

      result = MethodHandles.
         lookup().
         unreflectGetter(field);

      result = Preconditions.verifyNotNull(result, "result is null");
   }
   catch (NoSuchFieldException | SecurityException | IllegalAccessException e)
   {
      throw new ExceptionInInitializerError(e);
   }

   return(result);
}

private static MethodHandle initExpungeStaleEntries()
{
   MethodHandle result;
   Class<?> clazz;
   Method method;
   Object threadLocals;

   threadLocals = getThreadLocals();
   clazz        = threadLocals.getClass();

   try
   {
      method = clazz.getDeclaredMethod("expungeStaleEntries");

      method.setAccessible(true);

      result = MethodHandles.
         lookup().
         unreflect(method);
   }
   catch (NoSuchMethodException | SecurityException | IllegalAccessException e)
   {
      throw new ExceptionInInitializerError(e);
   }

   return(result);
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top