Das folgende Codefragment führt zwei Threads aus, einer ist eine einfache Timer-Protokollierung jede Sekunde, der zweite ist eine Endlosschleife, die eine Restoperation ausführt:
public class TestBlockingThread {
private static final Logger LOGGER = LoggerFactory.getLogger(TestBlockingThread.class);
public static final void main(String[] args) throws InterruptedException {
Runnable task = () -> {
int i = 0;
while (true) {
i++;
if (i != 0) {
boolean b = 1 % i == 0;
}
}
};
new Thread(new LogTimer()).start();
Thread.sleep(2000);
new Thread(task).start();
}
public static class LogTimer implements Runnable {
@Override
public void run() {
while (true) {
long start = System.currentTimeMillis();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// do nothing
}
LOGGER.info("timeElapsed={}", System.currentTimeMillis() - start);
}
}
}
}
Dies ergibt das folgende Ergebnis:
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=13331
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1006
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1003
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
[Thread-0] INFO c.m.c.concurrent.TestBlockingThread - timeElapsed=1004
Ich verstehe nicht, warum die unendliche Aufgabe alle anderen Threads für 13,3 Sekunden blockiert. Ich habe versucht, Thread-Prioritäten und andere Einstellungen zu ändern, nichts hat funktioniert.
Wenn Sie Vorschläge zur Behebung dieses Problems haben (einschließlich der Optimierung der Einstellungen für die Betriebssystemumschaltung), lassen Sie es mich bitte wissen.
java
multithreading
kms333
quelle
quelle
-XX:+PrintCompilation
mir folgenden zu der Zeit bekommt die verlängerten Verzögerungs Ende: TestBlockingThread :: lambda $ 0 @ 2 (24 Byte) KOMPILIERT SKIPPED: trivial Endlosschleife (Neuversuch bei verschiedenem Tiere)-Djava.compiler=NONE
, wird es nicht passieren.Antworten:
Nach all den Erklärungen hier (dank Peter Lawrey ) haben wir festgestellt, dass die Hauptursache für diese Pause darin besteht, dass der Sicherheitspunkt innerhalb der Schleife eher selten erreicht wird. Daher dauert es lange, bis alle Threads für das Ersetzen von JIT-kompiliertem Code gestoppt sind.
Aber ich beschloss, tiefer zu gehen und herauszufinden, warum der Sicherheitspunkt selten erreicht wird. Ich fand es etwas verwirrend, warum der Rücksprung der
while
Schleife in diesem Fall nicht "sicher" ist.Also rufe ich
-XX:+PrintAssembly
in all seiner Pracht, um zu helfenNach einigen Nachforschungen stellte ich fest, dass nach der dritten Neukompilierung des Lambda-
C2
Compilers Safepoint-Umfragen innerhalb der Schleife vollständig weggeworfen wurden.AKTUALISIEREN
Während der Profilierungsphase wurde die Variable
i
nie gleich 0 gesehen. Deshalb wurdeC2
dieser Zweig spekulativ weg optimiert, sodass die Schleife in so etwas wie transformiert wurdeBeachten Sie, dass die ursprüngliche Endlosschleife mit einem Zähler in eine reguläre Endlosschleife umgeformt wurde! Aufgrund der JIT-Optimierung zur Eliminierung von Safepoint-Abfragen in Schleifen mit endlicher Zählung gab es auch in dieser Schleife keine Safepoint-Abfrage.
Nach einiger Zeit wieder
i
eingewickelt0
, und die ungewöhnliche Falle wurde genommen. Die Methode wurde deoptimiert und im Interpreter weiter ausgeführt. Während der Neukompilierung mit neuem WissenC2
erkannte die Endlosschleife und gab die Kompilierung auf. Der Rest der Methode wurde im Dolmetscher mit geeigneten Sicherheitspunkten fortgesetzt.Es gibt einen großartigen Blog-Beitrag "Sicherheitspunkte: Bedeutung, Nebenwirkungen und Gemeinkosten" von Nitsan Wakart, der die Sicherheitspunkte und dieses spezielle Problem behandelt.
Die Eliminierung sicherer Punkte in sehr lang gezählten Schleifen ist bekanntermaßen ein Problem. Der Fehler
JDK-5014723
(dank Vladimir Ivanov ) behebt dieses Problem.Die Problemumgehung ist verfügbar, bis der Fehler endgültig behoben ist.
-XX:+UseCountedLoopSafepoints
(dies führt zu einer allgemeinen Leistungsminderung und kann zu einem Absturz der JVM führenJDK-8161147
). Nach der Verwendung desC2
Compilers bleiben die Sicherheitspunkte bei den Rücksprüngen erhalten, und die ursprüngliche Pause verschwindet vollständig.Sie können die Kompilierung problematischer Methoden mithilfe von explizit deaktivieren
-XX:CompileCommand='exclude,binary/class/Name,methodName'
Oder Sie können Ihren Code neu schreiben, indem Sie den Sicherheitspunkt manuell hinzufügen. Wenn Sie beispielsweise
Thread.yield()
am Ende des Zyklus anrufen oder sogarint i
zulong i
(danke, Nitsan Wakart ) wechseln , wird auch die Pause behoben .quelle
-XX:+UseCountedLoopSafepoints
in der Produktion verwenden, da dies zu einem Absturz der JVM führen kann . Die bisher beste Problemumgehung besteht darin, die lange Schleife manuell in kürzere zu unterteilen.c2
sicherungspunkte entfernt! Aber eine weitere Sache, die ich nicht verstanden habe, ist, was als nächstes passiert. Soweit ich sehen kann, gibt es nach dem Abrollen der Schleife (?) keine Sicherheitspunkte mehr und es sieht so aus, als gäbe es keine Möglichkeit, stw zu machen. Es kommt also zu einer Art Timeout und es findet eine De-Optimierung statt?i
ist niemals 0, daher wird die Schleife spekulativ infor (int i = osr_value; i != 0; i++) { if (1 % i == 0) uncommon_trap(); } uncommon_trap();
eine reguläre Schleife mit endlicher Zählung umgewandelt. Sobald deri
Wraps auf 0 zurückgesetzt wurde, wird die ungewöhnliche Falle genommen, die Methode wird deoptimiert und im Interpreter fortgesetzt. Während der Neukompilierung mit dem neuen Wissen erkennt JIT die Endlosschleife und gibt die Kompilierung auf. Der Rest der Methode wird im Interpreter mit geeigneten Sicherheitspunkten ausgeführt.Kurz gesagt, die Schleife, die Sie haben, hat keinen sicheren Punkt, außer wenn sie
i == 0
erreicht ist. Wenn diese Methode kompiliert wird und den zu ersetzenden Code auslöst, müssen alle Threads an einen sicheren Punkt gebracht werden. Dies dauert jedoch sehr lange und blockiert nicht nur den Thread, in dem der Code ausgeführt wird, sondern alle Threads in der JVM.Ich habe die folgenden Befehlszeilenoptionen hinzugefügt.
Ich habe den Code auch so geändert, dass Gleitkommazahlen verwendet werden, die anscheinend länger dauern.
Und was ich in der Ausgabe sehe, ist
Hinweis: Damit Code ersetzt werden kann, müssen Threads an einem sicheren Punkt gestoppt werden. Hier scheint es jedoch so zu sein, dass ein solcher sicherer Punkt sehr selten erreicht wird (möglicherweise nur beim
i == 0
Ändern der Aufgabe inIch sehe eine ähnliche Verzögerung.
Wenn Sie der Schleife vorsichtig Code hinzufügen, erhalten Sie eine längere Verzögerung.
bekommt
Ändern Sie den Code jedoch so, dass eine native Methode verwendet wird, die immer einen sicheren Punkt hat (wenn es sich nicht um eine intrinsische Methode handelt).
druckt
Hinweis: Durch Hinzufügen
if (Thread.currentThread().isInterrupted()) { ... }
zu einer Schleife wird ein sicherer Punkt hinzugefügt .Hinweis: Dies geschah auf einem 16-Kern-Computer, sodass es nicht an CPU-Ressourcen mangelt.
quelle
Fand die Antwort warum . Sie werden als Sicherheitspunkte bezeichnet und sind am besten als Stop-The-World bekannt, das aufgrund von GC auftritt.
Siehe diese Artikel: Protokollierung von Stop-the-World-Pausen in JVM
Beim Lesen des HotSpot-Glossars der Begriffe wird Folgendes definiert:
Wenn ich mit den oben genannten Flags laufe, erhalte ich folgende Ausgabe:
Beachten Sie das dritte STW-Ereignis:
Gesamtzeit gestoppt: 10.7951187 Sekunden Das
Stoppen von Threads dauerte: 10.7950774 Sekunden
JIT selbst so gut wie keine Zeit in Anspruch nahm, aber sobald die JVM eine JIT - Kompilierung durchzuführen entschieden hatte, ging es STW - Modus, aber da der Code (die Endlosschleife) kompiliert werden keine hat Aufrufort wurde kein Sicherungspunkt jemals erreicht.
Das STW endet, wenn JIT schließlich das Warten aufgibt und feststellt, dass sich der Code in einer Endlosschleife befindet.
quelle
Nachdem ich den Kommentarthreads und einigen Tests selbst gefolgt bin, glaube ich, dass die Pause vom JIT-Compiler verursacht wird. Warum der JIT-Compiler so lange dauert, kann ich nicht debuggen.
Da Sie jedoch nur gefragt haben, wie Sie dies verhindern können, habe ich eine Lösung:
Ziehen Sie Ihre Endlosschleife in eine Methode, mit der sie vom JIT-Compiler ausgeschlossen werden kann
Führen Sie Ihr Programm mit diesem VM-Argument aus:
-XX: CompileCommand = exclude, PACKAGE.TestBlockingThread :: infLoop (ersetzen Sie PACKAGE durch Ihre Paketinformationen)
Sie sollten eine Meldung wie diese erhalten, um anzugeben, wann die Methode JIT-kompiliert worden wäre:
### Ohne Kompilierung: statische Blockierung. TestBlockingThread :: infLoop
Möglicherweise stellen Sie fest, dass ich die Klasse in ein Paket namens Blockierung eingefügt habe
quelle
i == 0
while
Schleife kein sicherer Punkt?if (i != 0) { ... } else { safepoint(); }
aber dies ist sehr selten. dh. Wenn Sie die Schleife verlassen / unterbrechen, erhalten Sie fast die gleichen Timings.