Ein Memo, in dem experimentiert wurde, ob ein Thread, der in einer Endlosschleife in Java außer Kontrolle geraten ist, von außen gewaltsam gestoppt werden kann

Fazit

Fazit vom 21.03.2018:

Experimentierumgebung

Experimentiert mit zwei CentOS6 / 7-Typen (beide x86_64-Version). Beginnen Sie mit der Compute Engine von GCP und installieren Sie die folgenden Pakete.

sudo yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-devel
sudo yum groupinstall -y "Development tools"

Details zur Java-Version (bei der Überprüfung des 21.03.2018 wurde diese wie in der CentOS 6/7-Umgebung angezeigt)

$ java -version
openjdk version "1.8.0_161"
OpenJDK Runtime Environment (build 1.8.0_161-b14)
OpenJDK 64-Bit Server VM (build 25.161-b14, mixed mode)

$ javac -version
javac 1.8.0_161

Beispiel für eine Endlosschleife: InfiniteLoop.java

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class InfiniteLoop {
    static class Looper implements Runnable {
        @Override
        public void run() {
            int cnt = 0;
            while (true) {
                //Endlosschleife, die Interrupts ignoriert
                try {
                    System.out.println("InifiniteLoop-Looper count " + cnt);
                    Thread.sleep(2000);
                } catch (InterruptedException ignored) {
                }
                cnt++;
            }
        }
    }

    public static void main(String[] args) {
        ExecutorService pool = Executors.newSingleThreadExecutor();
        Future<?> f = pool.submit(new Looper());
        try {
            // Future.get()Warten, bis die Aufgabe beendet ist
            f.get();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

kompilieren:

$ javac InfiniteLoop.java

Experiment 1: Mit jdb anhängen und den Thread beenden

Referenz:

Führen Sie die InfiniteLoop-Klasse mit der JVM-Option aus, um sie später mit jdb zu verbinden.

$ java -Xrunjdwp:transport=dt_socket,address=9000,server=y,suspend=n InfiniteLoop

Holen Sie sich die Java-PID von einem anderen Terminal und überprüfen Sie den Java-Stack-Trace mit jstack.

$ pidof java
13667

$ jstack 13667
2018-03-21 07:52:59
Full thread dump OpenJDK 64-Bit Server VM (25.161-b14 mixed mode):
(...)

"pool-1-thread-1" #10 prio=5 os_prio=0 tid=0x00007f0d840f3b30 nid=0x356f waiting on condition [0x00007f0d6d8c8000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at InfiniteLoop$Looper.run(InfiniteLoop.java:14)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

(...)

Aus dem Stack-Trace konnten wir bestätigen, dass sich der Thread mit dem Namen "pool-1-thread-1" in einer Endlosschleife befand.

Nachdem wir den Threadnamen kennen, starten wir jdb, hängen ihn an die JVM an und beenden den Thread in der Schleife.

$ jdb -attach 9000
Set uncaught java.lang.Throwable
Set deferred uncaught java.lang.Throwable
Initializing jdb ...

(Threadliste anzeigen)
> threads
Group system:
  (java.lang.ref.Reference$ReferenceHandler)0x1b9 Reference Handler cond. waiting
  (java.lang.ref.Finalizer$FinalizerThread)0x1ba  Finalizer         cond. waiting
  (java.lang.Thread)0x1bb                         Signal Dispatcher running
Group main:
  (java.lang.Thread)0x1                           main              cond. waiting
  (java.lang.Thread)0x1bd                         pool-1-thread-1   sleeping

(Threadname"pool-1-thread-1",Stellen Sie sicher, dass 0x1bd der Thread in der Schleife ist.)

(Thread, den Sie stoppen möchten:Unterbrechen Sie 0x1bd und töten Sie, während Sie Exception nach dem Treten auslösen)
> thread 0x1bd
pool-1-thread-1[1] suspend 0x1bd
pool-1-thread-1[1] step
>
Step completed: "thread=pool-1-thread-1", InfiniteLoop$Looper.run(), line=16 bci=33
16                    }

pool-1-thread-1[1] kill 0x1bd new java.lang.Exception("kill from jdb")
killing thread: pool-1-thread-1
pool-1-thread-1[1] instance of java.lang.Thread(name='pool-1-thread-1', id=445) killed

(Beenden Sie den Debugger nach dem Neustart mit cont)
pool-1-thread-1[1] cont
> exit

Das Ausführungsergebnis von Infinite Loop wurde durch den Betrieb von jdb wie folgt beeinflusst.

$ java -Xrunjdwp:transport=dt_socket,address=9000,server=y,suspend=n InfiniteLoop
(...)
InifiniteLoop-Looper count 32
InifiniteLoop-Looper count 33
InifiniteLoop-Looper count 34
(Führen Sie suspend von jdb aus->Zählanzeige stoppt)
(Beim Töten von jdb wird Folgendes angezeigt)
java.util.concurrent.ExecutionException: java.lang.Exception: kill from jdb
        at java.util.concurrent.FutureTask.report(FutureTask.java:122)
        at java.util.concurrent.FutureTask.get(FutureTask.java:192)
        at InfiniteLoop.main(InfiniteLoop.java:27)
Caused by: java.lang.Exception: kill from jdb
        at InfiniteLoop$Looper.run(InfiniteLoop.java:16)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
(Wenn Sie jdb beenden, wird Folgendes angezeigt)
Listening for transport dt_socket at address: 9000
(Ctrl-Beenden Sie mit C.)

Es ist ersichtlich, dass die ausführbare Aufgabe durch Beenden von jdb beendet wird und die Wartezeit von Future.get () durch Auslösen einer ExecutionException freigegeben wird. Sie können auch sehen, dass die Ursache für die ExecutionException die Ausnahme ist, die durch das Beenden von jdb ausgelöst wird.

Experiment 2: Mit gdb anhängen und den Thread mit pthread_kill (2) beenden

Referenz:

Führen Sie die InfiniteLoop-Klasse ohne spezielle Debugging-Optionen aus.

$ java InfiniteLoop

Überprüfen Sie den Speicherort der ausführbaren Java-Datei:

$ which java
/usr/bin/java
$ ls -l /usr/bin/java
(...) /usr/bin/java -> /etc/alternatives/java
$ ls -l /etc/alternatives/java
(...) /etc/alternatives/java -> /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
$ ls -l /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java
(...) /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

Überprüfen Sie die Java-PID und erhalten Sie eine Stapelverfolgung.

$ jstack `pidof java`
2018-03-21 08:28:38
Full thread dump OpenJDK 64-Bit Server VM (25.161-b14 mixed mode):
(...)

"pool-1-thread-1" #8 prio=5 os_prio=0 tid=0x00007ffb500ed000 nid=0x6a05 waiting on condition [0x00007ffb54146000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at InfiniteLoop$Looper.run(InfiniteLoop.java:14)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
(...)

Unter Linux ist nid = 0x ... der von jstack angezeigten Thread-Informationen die von pthread_kill (2) angegebene LWP-ID.

Fügen Sie mit dem ausführbaren Dateinamen "gdb" PID "hinzu und führen Sie den Befehl" info threads "aus.

$ gdb /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java `pidof java`

(gdb) info threads
(...)
  4 Thread 0x7ffb54248700 (LWP 27140)  0x0000003e2020ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  3 Thread 0x7ffb54147700 (LWP 27141)  0x0000003e2020ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0
  2 Thread 0x7ffb40fff700 (LWP 27164)  0x0000003e2020eb2d in accept () from /lib64/libpthread.so.0
* 1 Thread 0x7ffb57342700 (LWP 27131)  0x0000003e202082fd in pthread_join () from /lib64/libpthread.so.0

Das Ergebnis der Ausführung des Befehls "info threads" mit gdb ist die Dezimalanzeige. Wenn Sie nach 27141 suchen, dem Endlosschleifen-Thread, der dieses Mal angezeigt wird, wobei nid = 0x6a05 in eine Dezimalzahl konvertiert wurde, entspricht der dritte Thread von unten diesem.

  3 Thread 0x7ffb54147700 (LWP 27141)  0x0000003e2020ba5e in pthread_cond_timedwait@@GLIBC_2.3.2 () from /lib64/libpthread.so.0

Also nennen wir hier call pthread_kill (pthread_t, int) ... ** Was soll ich für das erste Argument angeben? ** Welche der entsprechenden Zeilen des Ausführungsergebnisses "Info-Threads" sollte angegeben werden? ** Was sollte auch angegeben werden, damit das Signal des zweiten Arguments Javas Thread.stop () entspricht? ** ** **

** Wie kann ich zunächst genau das gleiche Verhalten aufrufen, wie wenn ein Thread in Java innerhalb des von gdb angehängten Prozesses beendet wird? ** ** **

→ Dies ist eine völlige Pattsituation. Selbst wenn ich versuchte, es richtig aufzurufen, endete der Java-Prozess mit SIGSEGV, und obwohl es nicht so viel war, führte es nicht dazu, dass "nur der außer Kontrolle geratene Thread isoliert und beendet wurde".

Selbst wenn gdb den Prozess des Auslösens einer Ausnahme von innen reproduzieren kann, wie beim Beenden mit jdb durch Lesen des Java-Quellcodes oder Parsen des Systemaufrufs mit strace, handelt es sich wahrscheinlich um eine beträchtliche interne Implementierung. Ist es nicht abhängig und nicht stabil und in einer tatsächlichen Betriebsumgebung leicht reproduzierbar?

Zusammenfassung vom 21.03.2018

Es war nicht ordentlich organisiert, aber das war's.

Recommended Posts

Ein Memo, in dem experimentiert wurde, ob ein Thread, der in einer Endlosschleife in Java außer Kontrolle geraten ist, von außen gewaltsam gestoppt werden kann
Ein Programm (Java), das die Summe von ungeraden und geraden Zahlen in einem Array ausgibt
Beispielprogramm, das den Hashwert einer Datei in Java zurückgibt
Die Geschichte, dass das Servlet nicht in die Java-Webanwendung geladen werden konnte
Ich wusste nicht, dass innere Klassen in der [Java] -Schnittstelle definiert werden können