Langsame Anwendung, häufige JVM hängt mit Einzel-CPU-Setups und Java 12+

23

Wir haben eine Client-Anwendung (mit mehr als 10 Jahren Entwicklungszeit). Das JDK wurde kürzlich von OpenJDK 11 auf OpenJDK 14 aktualisiert. Bei Windows 10-Setups mit einer CPU (Hyper-Threading deaktiviert) (und in VirtualBox-Computern mit nur einer verfügbaren CPU) wird die Anwendung im Vergleich zu Java 11 recht langsam gestartet. Außerdem wird die meiste Zeit 100% CPU verwendet. Wir könnten das Problem auch reproduzieren, wenn die Prozessoraffinität auf nur eine CPU ( c:\windows\system32\cmd.exe /C start /affinity 1 ...) eingestellt wird.

Einige Messungen beim Starten der Anwendung und Ausführen einer Abfrage mit minimaler manueller Interaktion auf meinem VirtualBox-Computer:

  • OpenJDK 11.0.2: 36 Sekunden
  • OpenJDK 13.0.2: ~ 1,5 Minuten
  • OpenJDK 13.0.2 mit -XX:-UseBiasedLocking: 46 Sekunden
  • OpenJDK 13.0.2 mit -XX:-ThreadLocalHandshakes: 40 Sekunden
  • OpenJDK 14: 5-6 Minuten
  • OpenJDK 14 mit -XX:-UseBiasedLocking: 3-3,5 Minuten
  • OpenJDK 15 EA Build 20: ~ 4,5 Minuten

Nur das verwendete JDK (und die genannten Optionen) wurden geändert. ( -XX:-ThreadLocalHandshakesist in Java 14 nicht verfügbar.)

Wir haben versucht zu protokollieren, was JDK 14 damit macht -Xlog:all=debug:file=app.txt:uptime,tid,level,tags:filecount=50.

Das Zählen der Protokollzeilen pro Sekunde scheint mit OpenJDK 11.0.2 recht reibungslos zu sein:

$ cat jdk11-log/app* | grep "^\[" | cut -d. -f 1 | cut -d[ -f 2 | sort | uniq -c | sort -k 2 -n
  30710 0
  44012 1
  55461 2
  55974 3
  27182 4
  41292 5
  43796 6
  51889 7
  54170 8
  58850 9
  51422 10
  44378 11
  41405 12
  53589 13
  41696 14
  29526 15
   2350 16
  50228 17
  62623 18
  42684 19
  45045 20

Auf der anderen Seite scheint OpenJDK 14 interessante Ruhephasen zu haben:

$ cat jdk14-log/app* | grep "^\[" | cut -d. -f 1 | cut -d[ -f 2 | sort | uniq -c | sort -k 2 -n
   7726 0
   1715 5
  10744 6
   4341 11
  42792 12
  45979 13
  38783 14
  17253 21
  34747 22
   1025 28
   2079 33
   2398 39
   3016 44

Was passiert also zwischen den Sekunden 1-4, 7-10 und 14-20?

...
[0.350s][7248][debug][class,resolve        ] jdk.internal.ref.CleanerFactory$1 java.lang.Thread CleanerFactory.java:45
[0.350s][7248][debug][class,resolve        ] jdk.internal.ref.CleanerImpl java.lang.Thread CleanerImpl.java:117
[0.350s][7248][info ][biasedlocking        ] Aligned thread 0x000000001727e010 to 0x000000001727e800
[0.350s][7248][info ][os,thread            ] Thread started (tid: 2944, attributes: stacksize: default, flags: CREATE_SUSPENDED STACK_SIZE_PARAM_IS)
[0.350s][6884][info ][os,thread            ] Thread is alive (tid: 6884).
[0.350s][6884][debug][os,thread            ] Thread 6884 stack dimensions: 0x00000000175b0000-0x00000000176b0000 (1024k).
[0.350s][6884][debug][os,thread            ] Thread 6884 stack guard pages activated: 0x00000000175b0000-0x00000000175b4000.
[0.350s][7248][debug][thread,smr           ] tid=7248: Threads::add: new ThreadsList=0x0000000017254500
[0.350s][7248][debug][thread,smr           ] tid=7248: ThreadsSMRSupport::free_list: threads=0x0000000017253d50 is freed.
[0.350s][2944][info ][os,thread            ] Thread is alive (tid: 2944).
[0.350s][2944][debug][os,thread            ] Thread 2944 stack dimensions: 0x00000000177b0000-0x00000000178b0000 (1024k).
[0.350s][2944][debug][os,thread            ] Thread 2944 stack guard pages activated: 0x00000000177b0000-0x00000000177b4000.
[0.351s][2944][debug][class,resolve        ] java.lang.Thread java.lang.Runnable Thread.java:832
[0.351s][2944][debug][class,resolve        ] jdk.internal.ref.CleanerImpl jdk.internal.misc.InnocuousThread CleanerImpl.java:135
[0.351s][2944][debug][class,resolve        ] jdk.internal.ref.CleanerImpl jdk.internal.ref.PhantomCleanable CleanerImpl.java:138
[0.351s][2944][info ][biasedlocking,handshake] JavaThread 0x000000001727e800 handshaking JavaThread 0x000000000286d800 to revoke object 0x00000000c0087f78
[0.351s][2944][debug][vmthread               ] Adding VM operation: HandshakeOneThread
[0.351s][6708][debug][vmthread               ] Evaluating non-safepoint VM operation: HandshakeOneThread
[0.351s][6708][debug][vmoperation            ] begin VM_Operation (0x00000000178af250): HandshakeOneThread, mode: no safepoint, requested by thread 0x000000001727e800

# no log until 5.723s

[5.723s][7248][info ][biasedlocking          ]   Revoked bias of currently-unlocked object
[5.723s][7248][debug][handshake,task         ] Operation: RevokeOneBias for thread 0x000000000286d800, is_vm_thread: false, completed in 94800 ns
[5.723s][7248][debug][class,resolve          ] java.util.zip.ZipFile$CleanableResource java.lang.ref.Cleaner ZipFile.java:715
[5.723s][7248][debug][class,resolve          ] java.lang.ref.Cleaner jdk.internal.ref.CleanerImpl$PhantomCleanableRef Cleaner.java:220
[5.723s][7248][debug][class,resolve          ] java.util.zip.ZipFile$CleanableResource java.util.WeakHashMap ZipFile.java:716
...

Die zweite Pause etwas später:

...
[6.246s][7248][info ][class,load              ] java.awt.Graphics source: jrt:/java.desktop
[6.246s][7248][debug][class,load              ]  klass: 0x0000000100081a00 super: 0x0000000100001080 loader: [loader data: 0x0000000002882bd0 of 'bootstrap'] bytes: 5625 checksum: 0025818f
[6.246s][7248][debug][class,resolve           ] java.awt.Graphics java.lang.Object (super)
[6.246s][7248][info ][class,loader,constraints] updating constraint for name java/awt/Graphics, loader 'bootstrap', by setting class object
[6.246s][7248][debug][jit,compilation         ]   19       4       java.lang.Object::<init> (1 bytes)   made not entrant
[6.246s][7248][debug][vmthread                ] Adding VM operation: HandshakeAllThreads
[6.246s][6708][debug][vmthread                ] Evaluating non-safepoint VM operation: HandshakeAllThreads
[6.246s][6708][debug][vmoperation             ] begin VM_Operation (0x000000000203ddf8): HandshakeAllThreads, mode: no safepoint, requested by thread 0x000000000286d800
[6.246s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026b0800, is_vm_thread: true, completed in 1400 ns
[6.246s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026bb800, is_vm_thread: true, completed in 700 ns
[6.246s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026ef800, is_vm_thread: true, completed in 100 ns
[6.246s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026f0800, is_vm_thread: true, completed in 100 ns
[6.246s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026f1800, is_vm_thread: true, completed in 100 ns
[6.246s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026f4800, is_vm_thread: true, completed in 100 ns
[6.247s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x0000000002768800, is_vm_thread: true, completed in 100 ns
[6.247s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x000000000276e000, is_vm_thread: true, completed in 100 ns
[6.247s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x0000000017268800, is_vm_thread: true, completed in 100 ns
[6.247s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x000000001727e800, is_vm_thread: true, completed in 800 ns

# no log until 11.783s

[11.783s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x000000000286d800, is_vm_thread: true, completed in 6300 ns
[11.783s][6708][info ][handshake               ] Handshake "Deoptimize", Targeted threads: 11, Executed by targeted threads: 0, Total completion time: 5536442500 ns
[11.783s][6708][debug][vmoperation             ] end VM_Operation (0x000000000203ddf8): HandshakeAllThreads, mode: no safepoint, requested by thread 0x000000000286d800
[11.783s][7248][debug][protectiondomain        ] Checking package access
[11.783s][7248][debug][protectiondomain        ] class loader: a 'jdk/internal/loader/ClassLoaders$AppClassLoader'{0x00000000c0058628} protection domain: a 'java/security/ProtectionDomain'{0x00000000c058b948} loading: 'java/awt/Graphics'
[11.783s][7248][debug][protectiondomain        ] granted
[11.783s][7248][debug][class,resolve           ] sun.launcher.LauncherHelper java.awt.Graphics LauncherHelper.java:816 (reflection)
[11.783s][7248][debug][class,resolve           ] jdk.internal.reflect.Reflection [Ljava.lang.reflect.Method; Reflection.java:300
[11.783s][7248][debug][class,preorder          ] java.lang.PublicMethods$MethodList source: C:\Users\example\AppData\Local\example\stable\jdk\lib\modules
...

Dann der dritte:

...
[14.578s][7248][debug][class,preorder          ] java.lang.InheritableThreadLocal source: C:\Users\example\AppData\Local\example\stable\jdk\lib\modules
[14.578s][7248][info ][class,load              ] java.lang.InheritableThreadLocal source: jrt:/java.base
[14.578s][7248][debug][class,load              ]  klass: 0x0000000100124740 super: 0x0000000100021a18 loader: [loader data: 0x0000000002882bd0 of 'bootstrap'] bytes: 1338 checksum: 8013ed55
[14.578s][7248][debug][class,resolve           ] java.lang.InheritableThreadLocal java.lang.ThreadLocal (super)
[14.578s][7248][debug][jit,compilation         ]  699       3       java.lang.ThreadLocal::get (38 bytes)   made not entrant
[14.578s][7248][debug][vmthread                ] Adding VM operation: HandshakeAllThreads
[14.578s][6708][debug][vmthread                ] Evaluating non-safepoint VM operation: HandshakeAllThreads
[14.578s][6708][debug][vmoperation             ] begin VM_Operation (0x000000000203d228): HandshakeAllThreads, mode: no safepoint, requested by thread 0x000000000286d800
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026b0800, is_vm_thread: true, completed in 1600 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026bb800, is_vm_thread: true, completed in 900 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026ef800, is_vm_thread: true, completed in 100 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026f0800, is_vm_thread: true, completed in 100 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026f1800, is_vm_thread: true, completed in 100 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x00000000026f4800, is_vm_thread: true, completed in 0 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x0000000002768800, is_vm_thread: true, completed in 0 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x000000000276e000, is_vm_thread: true, completed in 0 ns
[14.578s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x0000000017268800, is_vm_thread: true, completed in 0 ns
[14.579s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x000000001727e800, is_vm_thread: true, completed in 900 ns

# no log until 21.455s

[21.455s][6708][debug][handshake,task          ] Operation: Deoptimize for thread 0x000000000286d800, is_vm_thread: true, completed in 12100 ns
[21.455s][6708][info ][handshake               ] Handshake "Deoptimize", Targeted threads: 11, Executed by targeted threads: 0, Total completion time: 6876829000 ns
[21.455s][6708][debug][vmoperation             ] end VM_Operation (0x000000000203d228): HandshakeAllThreads, mode: no safepoint, requested by thread 0x000000000286d800
[21.455s][7248][debug][class,resolve           ] sun.security.jca.Providers java.lang.InheritableThreadLocal Providers.java:39
[21.455s][7248][info ][class,init              ] 1251 Initializing 'java/lang/InheritableThreadLocal'(no method) (0x0000000100124740)
[21.455s][7248][debug][class,resolve           ] java.lang.InheritableThreadLocal java.lang.ThreadLocal InheritableThreadLocal.java:57
[21.456s][7248][debug][class,preorder          ] sun.security.jca.ProviderList source: C:\Users\example\AppData\Local\example\stable\jdk\lib\modules
[21.456s][7248][info ][class,load              ] sun.security.jca.ProviderList source: jrt:/java.base
[21.456s][7248][debug][class,load              ]  klass: 0x00000001001249a8 super: 0x0000000100001080 loader: [loader data: 0x0000000002882bd0 of 'bootstrap'] bytes: 11522 checksum: bdc239d2
[21.456s][7248][debug][class,resolve           ] sun.security.jca.ProviderList java.lang.Object (super)
...

Die folgenden zwei Zeilen scheinen interessant zu sein:

[11.783s][6708][info ][handshake               ] Handshake "Deoptimize", Targeted threads: 11, Executed by targeted threads: 0, Total completion time: 5536442500 ns
[21.455s][6708][info ][handshake               ] Handshake "Deoptimize", Targeted threads: 11, Executed by targeted threads: 0, Total completion time: 6876829000 ns

Ist das normal, dass diese Handshakes 5,5 und 6,8 ​​Sekunden dauerten?

Ich habe die gleiche Verlangsamung (und ähnliche Protokolle) mit der Update4j-Demo-App (die völlig unabhängig von unserer Anwendung ist) festgestellt, die mit diesem Befehl ausgeführt wird:

Z:\swing>\jdk-14\bin\java -Xlog:all=debug:file=app.txt:uptime,tid,level,tags:filecount=50 \
    -jar update4j-1.4.5.jar --remote http://docs.update4j.org/demo/setup.xml

Worauf sollte ich achten, um unsere App bei Windows 10-Setups mit einer CPU wieder schneller zu machen? Kann ich dies beheben, indem ich etwas in unserer Anwendung ändere oder JVM-Argumente hinzufüge?

Ist das ein JDK-Fehler, sollte ich ihn melden?

Update 2020-04-25:

Soweit ich sehe, enthalten die Protokolldateien auch GC-Protokolle. Dies sind die ersten GC-Protokolle:

$ cat app.txt.00 | grep "\[gc"
[0.016s][7248][debug][gc,heap          ] Minimum heap 8388608  Initial heap 60817408  Maximum heap 1073741824
[0.017s][7248][info ][gc,heap,coops    ] Heap address: 0x00000000c0000000, size: 1024 MB, Compressed Oops mode: 32-bit
[0.018s][7248][info ][gc               ] Using Serial
[22.863s][6708][info ][gc,start                ] GC(0) Pause Young (Allocation Failure)
[22.863s][6708][debug][gc,heap                 ] GC(0) Heap before GC invocations=0 (full 0): def new generation   total 17856K, used 15936K [0x00000000c0000000, 0x00000000c1350000, 0x00000000d5550000)
...

Leider scheint es nicht verwandt zu sein, da es nach der dritten Pause beginnt.

Update 2020-04-26:

Mit OpenJDK 14 verwendet die Anwendung 100% CPU in meiner VirtualBox-Maschine (mit einer CPU) (läuft auf einer i7-6600U-CPU). Die virtuelle Maschine verfügt über 3,5 GB RAM. Laut Task Manager sind 40% + kostenlos und die Festplattenaktivität 0% (ich denke, dies bedeutet kein Austauschen). Durch Hinzufügen einer weiteren CPU zur virtuellen Maschine (und Aktivieren von Hyper-Threading für physische Maschinen) wird die Anwendung wieder schnell genug. Ich habe mich nur gefragt, ob es ein absichtlicher Kompromiss während der JDK-Entwicklung war, die Leistung auf (seltenen) Einzel-CPU-Computern zu verlieren, um die JVM auf Multicore- / Hyper-Threading-CPUs schneller zu machen.

Palacsint
quelle
3
Aktiviert -Xlog:all=debugdie GC-Protokollierung? Das wäre meine erste Vermutung für irgendwelche Pausen.
Kichik
Haben Sie versucht, mit einem Profiler zu arbeiten und die Ergebnisse zu vergleichen? Ich denke, das wäre die natürliche Sache.
Axel
1
Überprüfen Sie auch die Windows-Systemmeldungen und versuchen Sie es mit einem anderen Build für JDK 14. Wenn alles andere fehlschlägt, eskalieren Sie als Problem?
Khanna111
1
@ Yan.F: OpenJDK 11 wird nicht für immer unterstützt, es ist Zeit, die neuen Versionen und Fehler vorzubereiten. Darüber hinaus scheint es sich um einen JDK-Fehler zu handeln, der möglicherweise behoben wurde oder nicht, aber auch anderen helfen könnte. Jedenfalls ist es für mich meistens Neugier. Auf der anderen Seite möchte ich jetzt unseren Kunden als minimale Systemanforderungen unserer App mitteilen.
Palacsint
1
@ Khanna111: Ja, ich habe es gerade als Antwort geschrieben.
Palacsint

Antworten:

6

Aus meiner Erfahrung hängen Leistungsprobleme mit JDKs hauptsächlich mit einem der folgenden Probleme zusammen:

  • JIT-Zusammenstellung
  • VM-Konfiguration (Heap-Größen)
  • GC-Algorithmus
  • Änderungen in der JVM / JDK, die bekannte gut laufende Anwendungen beschädigen
  • (Oh, und ich habe vergessen, das Laden von Klassen zu erwähnen ...)

Wenn Sie nur die Standard-JVM-Konfiguration seit OpenJDK11 verwenden, sollten Sie möglicherweise einige der bekannteren Optionen auf feste Werte wie GC, Heap-Größe usw. setzen.

Möglicherweise kann ein grafisches Analysetool helfen, Ihr Problem aufzuspüren. Wie Retrace, AppDynamics oder FlightRecorder und dergleichen. Diese bieten mehr Übersicht über den Gesamtstatus von Heap, GC-Zyklen, RAM, Threads, CPU-Auslastung usw. zu einem bestimmten Zeitpunkt, als Protokolldateien bieten könnten.

Verstehe ich richtig, dass Ihre Anwendung innerhalb der ersten Sekunde nach der Ausführung (unter OpenJDK11) etwa 30710 Zeilen in das Protokoll schreibt? Warum schreibt es "nur" in der ersten Sekunde ungefähr 7k Zeilen unter OpenJDK14? Dies scheint für mich ein großer Unterschied für eine Anwendung zu sein, die gerade auf verschiedenen JVMs gestartet wurde ... Sind Sie sicher, dass beispielsweise nicht viele Exception-Stacktraces im Protokoll gespeichert sind?
Die anderen Zahlen sind manchmal sogar noch höher. Vielleicht hängen die Verlangsamungen mit der Protokollierung von Ausnahmen zusammen? Oder sogar tauschen, wenn der Arbeitsspeicher knapp wird?
Eigentlich denke ich, wenn eine Anwendung nichts in das Protokoll schreibt, ist dies ein Zeichen für einen reibungslosen Ablauf ohne Probleme (es sei denn, sie ist in dieser Zeit vollständig eingefroren). Was von den Sekunden 12-22 passiert (im Fall OpenJDK14 hier), würde mich mehr beschäftigen ... die protokollierten Leitungen gehen durch das Dach ... warum ?
Und danach geht die Protokollierung aller Zeit niedriger Werte von etwa 1-2k Linien nach unten ... was ist der Grund für die ?? (Nun, vielleicht ist es der GC, der in der zweiten 22 einspringt und eine Tabula Rasa macht, die einige Dinge löst ...?)

Eine andere Sache könnte Ihre Aussage über "Single-CPU" -Maschinen sein. Bedeutet dies auch "Single Core" (Idk, vielleicht ist Ihre Software auf ältere Hardware oder ähnliches zugeschnitten)? Und die "Single CPU" VMs laufen auf diesen Maschinen? Aber ich nehme an, ich irre mich in diesen Annahmen, da heutzutage fast alle CPUs Multicore sind ... aber ich würde vielleicht ein Multithreading-Problem (Deadlock) untersuchen.

Antares
quelle
2
Bitte verwenden Sie keine Signaturen oder Slogans in Ihren Posts. Das sich wiederholende "GL und HF" wird hier als Rauschen und Ablenkung vom Inhalt Ihres Posts angesehen. Weitere Informationen finden Sie unter meta.stackexchange.com/help/behavior .
meagar
1
"Verstehe ich richtig, dass Ihre Anwendung innerhalb der ersten Sekunde nach der Ausführung (unter OpenJDK11) etwa 30710 Zeilen in das Protokoll schreibt?" - Ja, du hast Recht.
Palacsint
1
"Sind Sie sicher, dass beispielsweise nicht viele Exception-Stacktraces in das Protokoll geschrieben wurden?" - Das Protokoll ist sauber, ich habe dort nichts Seltsames gefunden, die Anwendung funktioniert korrekt (außer dass sie sehr, sehr langsam läuft).
Palacsint
1
GC tritt in der 22. Sekunde an und die App bleibt danach langsam. Ich habe auch die Frage aktualisiert. Bitte beachten Sie, dass die Update4j-Demo-App das gleiche Problem hat. Danke für die Antwort!
Palacsint
1
Über 30.000 Log-Zeilen in einer Sekunde sind ziemlich groß ... stimmst du nicht zu? Ich frage mich wirklich, was nützlich sein könnte, um in so kurzer Zeit protokolliert zu werden, um diese große Anzahl von Protokollzeilen zu akzeptieren ... Haben Sie versucht, die Protokollierung vollständig zu deaktivieren und die Anwendung in diesem Modus zu profilieren? (Ich bin neugierig, aber vielleicht hat die Protokollierung wirklich keine Auswirkungen, wie Sie mit dem Verhalten von update4j implizieren)
Antares
5

Da "die meiste Zeit" 100% CPU verwendet wird und es mit Java 14 zehnmal länger (!) Dauert, bedeutet dies, dass Sie 90% Ihrer CPU in Java 14 verschwenden.

Wenn Sie nicht genügend Speicherplatz haben, können Sie dies tun, da Sie viel Zeit in GC verbringen, aber Sie scheinen dies ausgeschlossen zu haben.

Ich stelle fest, dass Sie die voreingenommene Sperroption optimieren und dass dies einen signifikanten Unterschied macht. Das sagt mir, dass Ihr Programm möglicherweise viel gleichzeitig in mehreren Threads arbeitet. Möglicherweise weist Ihr Programm einen Parallelitätsfehler auf, der in Java 14, jedoch nicht in Java 10 auftritt. Dies könnte auch erklären, warum das Hinzufügen einer weiteren CPU mehr als doppelt so schnell ist.

Parallelitätsfehler treten oft nur dann auf, wenn Sie Pech haben, und der Auslöser könnte wirklich alles gewesen sein, wie eine Änderung der Hashmap-Organisation usw.

Wenn es machbar ist, prüfen Sie zunächst, ob Schleifen vorhanden sind, die möglicherweise beschäftigt sind, anstatt zu schlafen.

Führen Sie dann einen Profiler im Stichprobenmodus aus (jvisualvm reicht aus) und suchen Sie nach Methoden, die einen viel größeren Prozentsatz der Gesamtzeit in Anspruch nehmen, als sie sollten. Da Ihre Leistung um den Faktor 10 abweicht, sollten alle Probleme dort wirklich herausspringen.

Matt Timmermans
quelle
Voreingenommenes Sperren war in der Vergangenheit notwendig, aber heutzutage nicht so sehr, und es wird vorgeschlagen, es standardmäßig zu deaktivieren und später zu entfernen: openjdk.java.net/jeps/374
JohannesB
2

Dies ist ein interessantes Problem, und es würde einen unbestimmten Aufwand erfordern, um es einzugrenzen, da es viele Permutationen und Kombinationen gibt, die ausprobiert und Daten gesammelt und zusammengestellt werden müssen.

Es scheint, dass es seit einiger Zeit keine Lösung dafür gibt. Möglicherweise muss dies eskaliert werden.

EDIT 2: Da "ThreadLocalHandshakes" veraltet ist und wir davon ausgehen können, dass das Sperren umstritten ist, schlagen Sie vor, ohne "UseBiasedLocking" zu versuchen, dieses Szenario hoffentlich zu beschleunigen.

Es gibt jedoch einige Vorschläge, um mehr Daten zu sammeln und zu versuchen, das Problem zu isolieren.

  1. Ordnen Sie mehr als einen Kern zu [Ich sehe, dass Sie es versucht haben und das Problem verschwindet. Scheint ein Problem mit der Ausführung eines Threads zu sein, das andere ausschließt. Siehe Nr. 7 unten)
  2. Weisen Sie mehr Heap zu (möglicherweise sind die Anforderungen von Version 14 höher als die von früheren JDKS).
  3. Weisen Sie dem Win 10 VB mehr Speicher zu.
  4. Überprüfen Sie die Betriebssystemmeldungen (in Ihrem Fall Win 10).
  5. Führen Sie es in einem nicht virtualisierten Win 10 aus.
  6. Versuchen Sie es mit einem anderen Build von JDK 14
  7. Führen Sie alle paar Intervalle (oder Profile) einen Thread-Dump durch. Analysieren Sie, welcher Thread ausschließlich ausgeführt wird. Vielleicht gibt es eine Einstellung für eine gerechte Zeitteilung. Möglicherweise wird ein Thread mit höherer Priorität ausgeführt. Was ist das für ein Thread und was macht er? Unter Linux können Sie die mit einem Prozess verknüpften Lightweight-Prozesse (Threads) und ihren Status in Echtzeit angeben. Ähnliches bei Win 10?
  8. CPU auslastung? 100% oder weniger? Eingeschränkt durch CPU oder mem? 100% CPU in Service-Threads? Welcher Service-Thread?
  9. Haben Sie explizit einen GC-Algo festgelegt?

Ich habe persönlich Probleme in Versionen gesehen, die mit GC, Heap-Größenänderung, Problemen mit virtualisierten Containern usw. zu tun haben.

Ich denke, darauf gibt es keine einfache Antwort, zumal diese Frage schon seit einiger Zeit besteht. Aber wir können alles Gute versuchen und uns wissen lassen, was das Ergebnis einiger dieser Isolationsschritte ist.

EDIT 1: Aus der aktualisierten Frage geht hervor, dass es sich um einen GC oder einen anderen Service-Thread handelt, der den einzelnen Kern nicht gerecht übernimmt (Thread-Local Handshakes).

Khanna111
quelle
Das Hinzufügen eines zusätzlichen CPU-Kerns, der verwendet wird, um den Wechsel von Java-Ergonomie auf 32-Bit-Systemen von einem Client zu einer Serverklasse vm mit unterschiedlichem GC und Tiered Compilation auszulösen, wenn dies immer noch der Fall ist, kann dies plötzliche Unterschiede in der Leistung und der Speichernutzung erklären, ja JVM Leistung ist kompliziert 😁
JohannesB
3
Die Java-Ergonomie (Standardeinstellungen) unterscheidet sich immer noch für 1 CPU (z. B. -XX: + UseSerialGC) oder 2 CPUs (z. B. G1GC, LoopStripMiningIter = 1000, ... ShortLoop = 100). Nachdem Sie dies jedoch mit -XX: + sichergestellt haben PrintFlagsFinal, dass ich alle Parameter auf das gleiche oder ein ähnliches laufendes Update4j optimiert habe, war immer noch extrem langsam, um mit nur einer anstelle von 2 CPUs mit cmd.exe / C Start / Affinität 0x1 zu starten (aber extrem schnell mit 0x3 - also mit 2 CPU (1 +) 10 binär)). Ich habe bestätigt, dass wir keinen Garbage Collector beschuldigen können, wenn wir Epsilon GC verwenden, mit dem GC-Overhead vermieden werden soll. TieredCompilation ist jedoch aktiviert
JohannesB
Aha. Mit Epsilon GC scheint es so langsam zu sein. In diesem Fall können Thread-Status und Dumps zur Bewertung, wo sie hängen bleiben, eine Möglichkeit sein. Sowohl in der Java-Welt als auch in der OS-Welt (Linux, wenn ich mich recht erinnere, war gcore)
Khanna111
2

TL; DR : Es ist eine OpenJDK-Regression.

Ich habe das nicht ausgenommen, aber ich konnte das Problem mit einer einfachen Hallo-Welt reproduzieren:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello world");
    }
}

Ich habe diese beiden Batch-Dateien verwendet:

main-1cpu.bat, was den javaProzess auf nur eine CPU beschränkt:

c:\windows\system32\cmd.exe /C start /affinity 1 \
    \jdk-14\bin\java \
    -Xlog:all=trace:file=app-1cpu.txt:uptime,tid,level,tags:filecount=50 \
    Main

main-full.batkann der javaProzess beide CPUs verwenden:

c:\windows\system32\cmd.exe /C start /affinity FF \
    \jdk-14\bin\java \
    -Xlog:all=trace:file=app-full.txt:uptime,tid,level,tags:filecount=50 \
    Main

(Die Unterschiede sind der affinityWert und der Name der Protokolldatei. Ich habe sie zum leichteren Lesen verpackt, aber umbrochen\ funktioniert unter Windows wahrscheinlich nicht.)

Einige Messungen unter Windows 10 x64 in VirtualBox (mit zwei CPUs):

PS Z:\main> Measure-Command { .\main-1cpu.bat }

...    
TotalSeconds      : 7.0203455
...


PS Z:\main> Measure-Command { .\main-full.bat }

...
TotalSeconds      : 1.5751352
...


PS Z:\main> Measure-Command { .\main-full.bat }

...
TotalSeconds      : 1.5585384
...


PS Z:\main> Measure-Command { .\main-1cpu.bat }

...
TotalSeconds      : 23.6482685
...

Die erstellten Tracelogs enthalten ähnliche Pausen, die Sie in der Frage sehen können.

Das Ausführen Mainohne Tracelogs ist schneller, aber der Unterschied zwischen der Einzel-CPU- und der Zwei-CPU-Version ist immer noch erkennbar: ~ 4-7 Sekunden gegenüber ~ 400 ms.

Ich habe diese Ergebnisse an die Mailingliste hotspot-dev @ openjdk gesendet und die Entwickler dort haben bestätigt, dass dies etwas ist, mit dem das JDK besser umgehen kann . Sie können vermeintliche Korrekturen auch im Thread finden. Hoffentlich wird es in OpenJDK 15 behoben.

Palacsint
quelle
1

Seien Sie vorsichtig bei der Protokollierung auf langsamen Festplatten, da dies Ihre Anwendung verlangsamt:

https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic

Es ist jedoch nicht wahrscheinlich, dass dies die Ursache des Problems ist, da die CPU immer noch ausgelastet ist und Sie dank des thread-lokalen Handshakes nicht darauf warten müssen, dass alle Threads einen sicheren Punkt erreichen: https: // openjdk. java.net/jeps/312

Auch nicht direkt mit dem Problem verbunden, aber allgemeiner, wenn Sie versuchen möchten, mehr Leistung für die Startzeit aus Ihrer Hardware herauszuholen, werfen Sie einen Blick auf AppCDS (Class Data Sharing):

https://blog.codefx.org/java/application-class-data-sharing/

JohannesB
quelle