web-dev-qa-db-fra.com

Dump de threads Java: thread bloqué sans "attente de verrouillage ..."

J'ai des difficultés à comprendre le vidage de thread que j'ai reçu de jstack pour une application Web Spring MVC s'exécutant sur Tomcat 6 (Java 1.6.0_22, Linux).

Je vois des threads bloquants (qui font attendre d'autres threads) qui sont bloqués, mais le vidage de threads ne me dit pas pourquoi ni pour quel moniteur ils attendent.

Exemple:

"TP-Processor75" daemon prio=10 tid=0x00007f3e88448800 nid=0x56f5 waiting for monitor entry [0x00000000472bc000]
    Java.lang.Thread.State: BLOCKED (on object monitor)
        at Java.lang.Class.initAnnotationsIfNecessary(Class.Java:3067)
        - locked <0x00007f3e9a0b3830> (a Java.lang.Class for org.catapultframework.resource.ResourceObject)
        at Java.lang.Class.getAnnotation(Class.Java:3029)
        ...

C'est à dire. Il me manque la ligne "En attente de verrouiller ..." dans la trace de la pile. Apparemment, le thread verrouille un objet Class, mais je ne vois pas pourquoi le thread lui-même est bloqué.

Le vidage de fil ne contient aucune astuce concernant les blocages.

Que puis-je faire pour identifier le moniteur de verrouillage?

Merci, Oliver

19
Oliver

Apparemment, la situation dans laquelle nous avons observé ces types de threads bloqués était liée à une consommation de mémoire importante et donc à un ramassage des ordures important. 

Cette question Problème de blocage de Java: Pourquoi la JVM bloquerait-elle les threads dans de nombreuses classes/méthodes? décrit une situation similaire, donc je crois que ces threads ont été simplement bloqués par le ramasse-miettes.

(Quoi qu'il en soit, après la résolution du problème de mémoire, ce problème lié aux threads bloquants avait disparu.)

10
Oliver

Vérifiez si le thread du finaliseur est bloqué ou en attente.

Lors d'un balayage de CPG, le CPG "arrêtera le monde" pour effectuer son nettoyage. La définition de "monde" dépend du ramasse-miettes utilisé et du contexte. Il peut s'agir d'un petit groupe de threads ou de tous. Avant de collecter officiellement les déchets, GC invoquera finalize () de l'objet.

Si vous êtes dans la situation indésirable dans laquelle vous implémentez des méthodes de finalisation, le code de finalisation risque de l'empêcher de se terminer et le «monde» reste arrêté.

Ceci est particulièrement évident lorsque de nombreux threads sont bloqués de manière permanente par une force magique inconnue: Recherchez le code où le blocage a lieu et cela n'aura aucun sens; il n'y a pas de code de blocage à trouver à proximité de celui-ci et les sauvegardes ne divulgueront pas le moniteur sur lequel il est en attente car il n'y en a pas. Le GC a suspendu les discussions.

4
user515655

J'ai eu un problème similaire tout à l'heure en utilisant une applet dans Google Chrome.

En bref:

  • Les threads BLOCKED peuvent être bloqués lorsque VM doit charger une classe.
  • Lorsque le processus de chargement de la classe elle-même est bloqué par quelque chose, un gel de l'application peut se produire.

En détail:

J'ai eu le scénario suivant:

  1. J'utilise une applet dans Chrome avec codebase = dossier vers des fichiers de classe uniques (pas de jar)
  2. Le site Web transmet les événements focus à l'applet à l'aide de LiveConnect
  3. Les appels JS entrants utilisent une Executor avec new Runnable() ... pour détacher les appels afin de réduire les temps d'attente et se bloque donc dans JS.
  4. C'est là que le problème s'est posé!

Explication:

  • La new Runnable() est une classe interne annonymous qui n'a pas été chargée avant l'appel de JS.
  • L'appel JS déclenche donc le chargement de la classe.
  • Mais maintenant, le chargeur de classe est bloqué car il doit communiquer avec le navigateur (je suppose) via la même file d'attente ou le même mécanisme que celui qui traite l'appel JS entrant.

Voici le thread bloqué qui tente de charger la classe:

"Thread-20" daemon prio=4 tid=0x052e8400 nid=0x4608 in Object.wait() [0x0975d000]
   Java.lang.Thread.State: WAITING (on object monitor)
    at Java.lang.Object.wait(Native Method)
    at Sun.plugin2.message.Queue.waitForMessage(Unknown Source)
    - locked <0x29fbc5d8> (a Sun.plugin2.message.Queue)
    at Sun.plugin2.message.Pipe$2.run(Unknown Source)
    at com.Sun.deploy.util.Waiter$1.wait(Unknown Source)
    at com.Sun.deploy.util.Waiter.runAndWait(Unknown Source)
    at Sun.plugin2.message.Pipe.receive(Unknown Source)
    at Sun.plugin2.main.client.MessagePassingExecutionContext.doCookieOp(Unknown Source)
    at Sun.plugin2.main.client.MessagePassingExecutionContext.getCookie(Unknown Source)
    at Sun.plugin2.main.client.PluginCookieSelector.getCookieFromBrowser(Unknown Source)
    at com.Sun.deploy.net.cookie.DeployCookieSelector.getCookieInfo(Unknown Source)
    at com.Sun.deploy.net.cookie.DeployCookieSelector.get(Unknown Source)
    - locked <0x298da868> (a Sun.plugin2.main.client.PluginCookieSelector)
    at Sun.net.www.protocol.http.HttpURLConnection.setCookieHeader(Unknown Source)
    at Sun.net.www.protocol.http.HttpURLConnection.writeRequests(Unknown Source)
    at Sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
    - locked <0x2457cdc0> (a Sun.net.www.protocol.http.HttpURLConnection)
    at com.Sun.deploy.net.HttpUtils.followRedirects(Unknown Source)
    at com.Sun.deploy.net.BasicHttpRequest.doRequest(Unknown Source)
    at com.Sun.deploy.net.BasicHttpRequest.doGetRequestEX(Unknown Source)
    at com.Sun.deploy.cache.ResourceProviderImpl.checkUpdateAvailable(Unknown Source)
    at com.Sun.deploy.cache.ResourceProviderImpl.isUpdateAvailable(Unknown Source)
    at com.Sun.deploy.cache.DeployCacheHandler.get(Unknown Source)
    - locked <0x245727a0> (a Java.lang.Object)
    at Sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source)
    at Sun.net.www.protocol.http.HttpURLConnection.connect(Unknown Source)
    at Sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
    - locked <0x24572020> (a Sun.net.www.protocol.http.HttpURLConnection)
    at Java.net.HttpURLConnection.getResponseCode(Unknown Source)
    at Sun.plugin2.applet.Applet2ClassLoader.getBytes(Unknown Source)
    at Sun.plugin2.applet.Applet2ClassLoader.access$000(Unknown Source)
    at Sun.plugin2.applet.Applet2ClassLoader$1.run(Unknown Source)
    at Java.security.AccessController.doPrivileged(Native Method)
    at Sun.plugin2.applet.Applet2ClassLoader.findClass(Unknown Source)
    at Sun.plugin2.applet.Plugin2ClassLoader.loadClass0(Unknown Source)
    at Sun.plugin2.applet.Plugin2ClassLoader.loadClass(Unknown Source)
    - locked <0x299726b8> (a Sun.plugin2.applet.Applet2ClassLoader)
    at Sun.plugin2.applet.Plugin2ClassLoader.loadClass(Unknown Source)
    - locked <0x299726b8> (a Sun.plugin2.applet.Applet2ClassLoader)
    at Java.lang.ClassLoader.loadClass(Unknown Source)

Comme vous pouvez le voir, il attend un message -> waitForMessage().

En même temps, notre appel entrant JS est bloqué ici:

"Applet 1 LiveConnect Worker Thread" prio=4 tid=0x05231800 nid=0x1278 waiting for monitor entry [0x0770e000]
   Java.lang.Thread.State: BLOCKED (on object monitor)
    at MyClass.myMethod(MyClass.Java:23)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at Java.lang.reflect.Method.invoke(Unknown Source)
    at Sun.plugin.javascript.Trampoline.invoke(Unknown Source)
    at Sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at Sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at Sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at Java.lang.reflect.Method.invoke(Unknown Source)
    at Sun.plugin.javascript.JSClassLoader.invoke(Unknown Source)
    at Sun.plugin2.liveconnect.JavaClass$MethodInfo.invoke(Unknown Source)
    at Sun.plugin2.liveconnect.JavaClass$MemberBundle.invoke(Unknown Source)
    at Sun.plugin2.liveconnect.JavaClass.invoke0(Unknown Source)
    at Sun.plugin2.liveconnect.JavaClass.invoke(Unknown Source)
    at Sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo$DefaultInvocationDelegate.invoke(Unknown Source)
    at Sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo$3.run(Unknown Source)
    at Java.security.AccessController.doPrivileged(Native Method)
    at Sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo.doObjectOp(Unknown Source)
    at Sun.plugin2.main.client.LiveConnectSupport$PerAppletInfo$LiveConnectWorker.run(Unknown Source)
    at Java.lang.Thread.run(Unknown Source)

D'autres threads supplémentaires ont été bloqués de la même manière. Je suppose que toutes les demandes de chargement de classe suivantes ont été bloquées par le premier thread de chargement de classe bloqué.

Comme mentionné précédemment, je suppose que le processus de chargement de classe est bloqué par l'appel JS en attente, lui-même bloqué par la classe manquante à charger.

Solutions:

  1. Déclenche le chargement de toutes les classes pertinentes dans le constructeur de l'applet avant que des appels puissent être passés à partir de JS.
  2. Cela pourrait aider si les fichiers de classe ne sont pas chargés individuellement, mais à partir d'un fichier jar. La théorie sous-jacente est la suivante: le chargeur de classes n’a pas besoin de parler au navigateur pour charger les classes à partir du fichier jar (qui serait 
  3. En combinaison avec 1 .: Utilisez une classe dynamique Proxy pour encapsuler tous les appels JS entrants et les exécuter indépendamment dans un Executor

Mon implémentation pour # 3:

public class MyClass implements JsCallInterface
{

    private final JsCallInterface jsProxy;

    private final static interface JsCallInterface
    {
        public void myMethod1Intern(String param1, String param2);
    }

    private final class JsCallRunnable implements Runnable
    {

        private final Method method;
        private final Object[] args;

        private JsCallRunnable(Method method, Object[] args)
        {
            this.method = method;
            this.args = args;
        }

        public void run()
        {
            try
            {
                method.invoke(MyClass.this, args);
            }
            catch (Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    public MyClass()
    {
        MyUtilsClass.class.getName(); // load class
        JsCallRunnable.class.getName(); // load class
        InvocationHandler jsCallHandler = new InvocationHandler()
        {

            public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable
            {
                MyUtilsClass.executeInExecutor(new JsCallRunnable(method, args));
                return null;
            }

        };
        jsProxy = (JsCallInterface) Proxy.newProxyInstance(MyClass.class.getClassLoader(), new Class<?>[] { JsCallInterface.class }, jsCallHandler);
    }

    public void myMethod1(String param1, String param2)
    {
        jsProxy.myMethod1Intern(param1, param2);
        // needs to be named differently than the external method or else the proxy will call this method recursively
        // alternatively the target-class in "method.invoke(MyClass.this, args);" could be a different instance of JsCallInterface
    }

    public void myMethod1Intern(String param1, String param2)
    {
        // do actual work here
    }
}
2

Il s’agit d’un bogue esthétique de la machine virtuelle HotSpot JVM d’Oracle - dans votre trace de pile où vous voyez - locked <0x00007f3e9a0b3830>, il devrait en réalité indiquer - waiting to lock <0x00007f3e9a0b3830>.

Voir ce bug pour plus de détails.

0
rxg