Comprendre le bogue en l'implémentant et en l'analysant (1) Deadlock (Java)

Aperçu

Il existe de nombreuses façons d'analyser les problèmes tels que les fuites de mémoire et les blocages sur Internet. Cependant, comme on ne sait pas quand et quel type de problème apparaîtra devant vous, vous apprendrez et pratiquerez systématiquement la méthode d'analyse avant de faire face au problème. Je pense que c'est assez difficile de le garder.

Par conséquent, nous avons développé une application Web remplie de bogues qui peut intentionnellement reproduire le problème afin que nous puissions apprendre dans un format pratique, et aussi [It] à Qiita. Des informations sur](http://qiita.com/tamura__246/items/7275c7406706fb0057e8) ont été publiées. Cette application Web est très facile à créer et à démarrer avec une seule commande, je pense donc qu'elle peut également être utilisée à des fins expérimentales et éducatives.

A l'aide de cette application web, je voudrais présenter différents problèmes (bugs) et comment les analyser petit à petit.

Donc, le premier bogue que je vais introduire est un thread Java ** deadlock **.

Reproduire le dead lock

Tout d'abord, reproduisons le verrou mort. Téléchargez et lancez l'application Web (https://github.com/k-tamura/easybuggy/releases/latest).

$ java -jar easybuggy.jar

Ou

$ git clone https://github.com/k-tamura/easybuggy
$ cd easybuggy
$ mvn clean install exec:exec

Vous pouvez également le démarrer en déployant ROOT.war sur un conteneur de servlet tel que Tomcat. Lorsque le message suivant s'affiche, l'application Web a été lancée.

5 27, 2017 3:29:58 pm org.apache.coyote.AbstractProtocol start
information: Starting ProtocolHandler ["http-bio-8080"]
> set path=%path%;C:\Program Files\Java\jdk1.8.0_121\bin
> "C:\Program Files\Java\jdk1.8.0_121\jre\bin\java" -jar easybuggy.jar

Après le démarrage, accédez à l'URL suivante.

http://localhost:8080

S'il démarre normalement, l'écran suivant s'affiche.

mainpage_ja.png

Cliquez sur le lien intitulé "Dead Lock (Java)". Ensuite, vous verrez un écran qui dit uniquement "Le chargement de cette page plusieurs fois entraînera un blocage". Tout d'abord, essayez de charger l'écran en appuyant une seule fois sur la touche F5. Après environ 5 secondes, une réponse sera renvoyée et le message "Aucun blocage ne s'est produit" sera affiché. Les blocages ne se produisent pas dans les situations où une seule demande est en cours de traitement. Appuyez ensuite deux fois de suite sur la touche F5. Cette fois, peu importe combien de temps vous attendez, vous ne devriez pas obtenir de réponse. À ce stade, l'application Web est verrouillée.

Analyser

Si aucune réponse n'est renvoyée, les possibilités suivantes sont possibles.

-Le traitement est arrêté (provoqué par un verrou mortel, etc.) -Le traitement n'est pas terminé (causé par une boucle infinie, etc.) -Le traitement prend plus de temps que prévu (en raison d'une logique qui ne tient pas compte des performances, etc.)

Si vous n'obtenez pas de réponse, la première chose à faire est de ** obtenir un vidage de thread **.

Tout d'abord, utilisez la commande jps pour identifier l'ID de processus de l'application Web.

$ jps
85489 org.eclipse.equinox.launcher_1.3.100.v20150511-1540.jar
45733 Launcher
69970 easybuggy.jar
89901 Jps

Le nom de processus de cette application Web est "easybuggy.jar" lorsqu'elle est lancée par la commande java et "EmbeddedJettyServer" lorsqu'elle est lancée par la commande mvn. Dans le cas ci-dessus, 69970 serait l'ID de processus.

Lorsque vous exécutez la commande jstack avec l'ID de processus spécifié spécifié, un vidage de thread est généré.

$ jstack 69970
2017-02-26 18:58:57
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode):

"Attach Listener" #71 daemon prio=9 os_prio=0 tid=0x00007f4804001800 nid=0x1120f runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"derby.rawStoreDaemon" #70 daemon prio=5 os_prio=0 tid=0x00007f4798deb800 nid=0x111b2 in Object.wait() [0x00007f47e0473000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
	at java.lang.Object.wait(Native Method)
	at org.apache.derby.impl.services.daemon.BasicDaemon.rest(Unknown Source)
	- locked <0x00000000f7aa3a80> (a org.apache.derby.impl.services.daemon.BasicDaemon)
	at org.apache.derby.impl.services.daemon.BasicDaemon.run(Unknown Source)
	at java.lang.Thread.run(Thread.java:745)

・ ・ ・(Abréviation)・ ・ ・

Found one Java-level deadlock:
=============================
"http-listener(8)":
  waiting to lock monitor 0x00007f4830363368 (object 0x00000000f0165dc8, a java.lang.Object),
  which is held by "http-listener(9)"
"http-listener(9)":
  waiting to lock monitor 0x00007f4830364b78 (object 0x00000000f0165db8, a java.lang.Object),
  which is held by "http-listener(8)"

Java stack information for the threads listed above:
===================================================
"http-listener(8)":
	at org.t246osslab.easybuggy.troubles.DeadlockServlet.lock12(DeadlockServlet.java:61)
	- waiting to lock <0x00000000f0165dc8> (a java.lang.Object)
	- 
	- locked <0x00000000f0165db8> (a java.lang.Object)
	at org.t246osslab.easybuggy.troubles.DeadlockServlet.doGet(DeadlockServlet.java:43)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)

・ ・ ・(Abréviation)・ ・ ・

"http-listener(9)":
	at org.t246osslab.easybuggy.troubles.DeadlockServlet.lock21(DeadlockServlet.java:70)
	- waiting to lock <0x00000000f0165db8> (a java.lang.Object)
	- locked <0x00000000f0165dc8> (a java.lang.Object)
	at org.t246osslab.easybuggy.troubles.DeadlockServlet.doGet(DeadlockServlet.java:46)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:687)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:790)

・ ・ ・(Abréviation)・ ・ ・

Found 1 deadlock.

Vous pouvez voir que le blocage s'est produit car il dit "Un blocage au niveau Java trouvé:". Les blocages peuvent être facilement détectés par les vidages de threads et peuvent également identifier le thread à l'origine du problème. Ce qui précède indique qu'il y a un blocage entre les deux threads "http-listener (8)" et "http-listener (9)". Vous pouvez voir que le premier attend de se verrouiller sur la ligne 61 de DeadlockServlet.java et le second est sur la ligne 70 de DeadlockServlet.java.

Qu'est-ce que Dead Lock?

Quel type d'événement est une impasse en premier lieu? Le verrouillage mort signifie que plusieurs processus (threads dans ce cas) attendent la libération des ressources (objets dans ce cas) qui sont occupés (verrouillés) les uns par les autres, et le traitement s'arrête.

Par exemple, après que le fil 1 verrouille l'objet 1 et que le fil 2 verrouille l'objet 2, les deux ne sont pas déverrouillés, mais le fil 1 verrouille l'objet 2 et le fil 2 verrouille l'objet 1 comme illustré ci-dessous. Si vous essayez, les deux threads attendront l'un l'autre pour se terminer et le processus s'arrêtera. Cet état est verrouillé.

a691a79f-30c0-33e3-9731-d0a6814d5fc0.png

De quel type de mise en œuvre s'agit-il

Quelle a été l'implémentation du code source? DeadlockServlet.java qui a été renvoyé au vidage de thread plus tôt Regardons. Les parties importantes sont:

    private final Object lock1 = new Object();
    private final Object lock2 = new Object();
    private boolean switchFlag = true;

    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

・ ・ ・(Abréviation)・ ・ ・

                switchFlag = !switchFlag;
                if (switchFlag) {
                    lock12();
                } else {
                    lock21();
                }

・ ・ ・(Abréviation)・ ・ ・

    }

    private void lock12() {
        synchronized (lock1) {
            sleep();
            synchronized (lock2) {
                sleep();
            }
        }
    }

    private void lock21() {
        synchronized (lock2) {
            sleep();
            synchronized (lock1) {
                sleep();
            }
        }
    }

Dans ce servlet, un drapeau appelé switchFlag alterne les requêtes entre les deux méthodes lock12 () et lock21 (). Dans lock12 (), l'objet "lock1" est verrouillé → sleep pendant 5 secondes → l'objet "lock2" est verrouillé dans cet ordre. Dans lock21 (), le traitement est exécuté dans l'ordre inverse.

Par conséquent, ce cas est le suivant.

dlockinapp.png

Deux threads d'écoute HTTP (http-listener (8) et http-listener (9)) qui ont été créés pour accepter les requêtes des clients attendent que leurs objets verrouillés respectifs (lock1, lock2) soient libérés. C'est pourquoi c'est une impasse.

Méthodes de détection autres que le vidage de thread

Il existe d'autres moyens de détecter les blocages.

VisualVM

Vous pouvez également détecter les blocages à l'aide de Visual VM fourni avec le JDK d'Oracle. VisualVM détecte automatiquement les blocages et affiche un avertissement en rouge.

visualvm.png

Java Mission Control

Si vous utilisez Java Mission Control, également fourni avec le JDK d'Oracle, vous pouvez le détecter en cochant la case "Détection de blocage".

JMC.png

JConsole

L'OpenJDK n'a pas de Visual VM ou Java Mission Control, mais il a une JConsole. Vous pouvez également le détecter en cliquant sur le bouton "Détecter le blocage mort".

JConsole.png

La machine virtuelle Java peut-elle détecter un blocage et mettre fin à un thread?

En général SGBDR, lorsqu'un verrou mort est détecté, une transaction peut être annulée et l'autre peut être validée. La JVM peut-elle faire de même? La réponse est non **". Il n'existe pas d'option JVM de ce type. La seule façon de ** restaurer une application Web à partir d'un blocage est de redémarrer **. En général, les blocages apparaissent rarement immédiatement, il peut donc arriver qu'un grand nombre de threads attendent. Par conséquent, lors de l'implémentation du processus de verrouillage d'un objet, ** le programmeur doit faire attention à ne pas provoquer une incohérence dans l'ordre des processus **.

Détecter le blocage par programme

Vous pouvez détecter les blocages par programme. Vous pouvez utiliser java.lang.management.ThreadMXBean.findDeadlockedThreads () pour obtenir des informations sur les threads bloqués comme indiqué ci-dessous.

        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        long[] threadIds = bean.findDeadlockedThreads();
        if (threadIds != null) {
            ThreadInfo[] infos = bean.getThreadInfo(threadIds);
            for (ThreadInfo info : infos) {
                log.error(info.toString());
            }
        }

Ce résumé


référence: COMP 3400 Lecture 7: Deadlock (Matériel de conférence de l'Université Ottervine?) http://faculty.otterbein.edu/PSanderson/COMP3400/notes/lecture07.html


[^ 1]: probablement à cause d'un bug dans Payara Micro, mais la cause n'a pas été identifiée. Je la chercherai quand j'aurai le temps.

Recommended Posts

Comprendre le bogue en l'implémentant et en l'analysant (1) Deadlock (Java)
Comprendre le bogue en l'implémentant et en l'analysant (2) Deadlock (SQL)
Comprendre le modèle Singleton en comparant le code Java et JavaScript
Comprendre le modèle Iterator en comparant le code Java et JavaScript
Java passe par valeur et passe par référence
[Java] Comprendre la différence entre List et Set
[Swift vs Java] Comprenons le statique et le final
[Java] Comprenez en 10 minutes! Tableau associatif et HashMap