Nous avons une application client (avec plus de 10 ans de développement). Son JDK a été récemment mis à niveau d'OpenJDK 11 vers OpenJDK 14. Sur les configurations Windows 10 à processeur unique (hyper-threading désactivé) (et à l'intérieur des machines VirtualBox avec un seul processeur disponible), l'application démarre assez lentement par rapport à Java 11. De plus, elle utilise 100% de CPU la plupart du temps. Nous pourrions également reproduire le problème en définissant l'affinité du processeur sur un seul processeur ( c:\windows\system32\cmd.exe /C start /affinity 1 ...
).
Quelques mesures avec le démarrage de l'application et l'exécution d'une requête avec une interaction manuelle minimale dans ma machine VirtualBox:
- OpenJDK 11.0.2: 36 secondes
- OpenJDK 13.0.2: ~ 1,5 minutes
- OpenJDK 13.0.2 avec
-XX:-UseBiasedLocking
: 46 secondes - OpenJDK 13.0.2 avec
-XX:-ThreadLocalHandshakes
: 40 secondes - OpenJDK 14: 5-6 minutes
- OpenJDK 14 avec
-XX:-UseBiasedLocking
: 3-3,5 minutes - OpenJDK 15 EA Build 20: ~ 4,5 minutes
Seul le JDK utilisé (et les options mentionnées) ont été modifiés. ( -XX:-ThreadLocalHandshakes
n'est pas disponible dans Java 14.)
Nous avons essayé de consigner ce que fait le JDK 14 -Xlog:all=debug:file=app.txt:uptime,tid,level,tags:filecount=50
.
Compter les lignes de journal pour chaque seconde semble assez simple avec OpenJDK 11.0.2:
$ 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
En revanche, OpenJDK 14 semble avoir des périodes de silence intéressantes:
$ 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
Alors, que se passe-t-il entre les secondes 1-4, 7-10 et 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
...
La deuxième pause un peu plus tard:
...
[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
...
Puis le troisième:
...
[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)
...
Les deux lignes suivantes semblent intéressantes:
[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
Est-ce normal que ces poignées de main aient pris 5,5 et 6,8 secondes?
J'ai connu le même ralentissement (et des journaux similaires) avec l' application de démonstration update4j (qui n'a aucun lien avec notre application) exécutée avec cette commande:
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
Que dois-je rechercher pour accélérer à nouveau notre application sur les configurations Windows 10 à processeur unique? Puis-je résoudre ce problème en modifiant quelque chose dans notre application ou en ajoutant des arguments JVM?
Est-ce un bug JDK, dois-je le signaler?
mise à jour 2020-04-25:
Pour autant que je vois, les fichiers journaux contiennent également des journaux GC. Ce sont les premiers journaux GC:
$ 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)
...
Malheureusement, il ne semble pas lié car il commence après la troisième pause.
mise à jour 2020-04-26:
Avec OpenJDK 14, l'application utilise 100% de CPU dans ma machine VirtualBox (à CPU unique) (fonctionnant sur un CPU i7-6600U). La machine virtuelle a 3,5 Go de RAM. Selon le Gestionnaire des tâches, 40% + est gratuit et l'activité du disque est de 0% (je suppose que cela signifie aucun échange). L'ajout d'un autre processeur à la machine virtuelle (et l'activation de l'hyper-threading pour les machines physiques) accélèrent à nouveau l'application. Je me demandais simplement, était-ce un compromis intentionnel lors du développement du JDK pour réduire les performances sur les machines à processeur unique (rares) pour rendre la JVM plus rapide sur les processeurs multicœurs / hyper-threading?
-Xlog:all=debug
N'activer la journalisation GC? Ce serait ma première supposition pour toute pause.Réponses:
D'après mon expérience, les problèmes de performances avec les JDK sont principalement liés à l'un des éléments suivants:
Si vous utilisez simplement la configuration JVM par défaut depuis OpenJDK11, vous devriez peut-être définir certaines des options les plus importantes sur des valeurs fixes, comme GC, taille de segment de mémoire, etc.
Peut-être qu'un outil d'analyse graphique pourrait aider à suivre votre problème. Comme Retrace, AppDynamics ou FlightRecorder et similaires. Celles-ci donnent plus de vue d'ensemble sur l'état général du tas, des cycles gc, de la RAM, des threads, de la charge CPU et ainsi de suite à un moment donné que les fichiers journaux ne pouvaient fournir.
Dois-je bien comprendre que votre application écrit environ 30710 lignes dans le journal dans la première seconde de l'exécution (sous OpenJDK11)? Pourquoi écrit-il "seulement" environ 7k lignes sous OpenJDK14 dans la première seconde? Cela semble être une énorme différence pour une application qui vient de démarrer sur différentes machines virtuelles Java pour moi ... Êtes-vous sûr qu'il n'y a pas par exemple de grandes quantités de traces de pile d'exceptions déversées dans le journal?
Les autres chiffres sont parfois encore plus élevés, alors peut-être que les ralentissements sont liés à la journalisation des exceptions? Ou même l'échange, si la RAM devient faible?
En fait, je pense que si une application n'écrit rien dans le journal, c'est un signe de bon fonctionnement sans problème (sauf si elle est complètement gelée dans ce temps). Que se passe-t-il entre les secondes 12 et 22 (dans le cas d'OpenJDK14 ici) est ce qui m'inquiète le plus ... les lignes enregistrées passent par le toit ... pourquoi ?
Et après, la journalisation descend à des valeurs basses de tous les temps d'environ 1-2k lignes ... quelle est la raison de cela ?? (eh bien, c'est peut-être le gc qui intervient au deuxième 22 et une tabula rasa qui résout certaines choses ...?)
Une autre chose peut être votre déclaration sur les machines "à processeur unique". Cela implique-t-il également un «noyau unique» (Idk, peut-être que votre logiciel est conçu sur du matériel hérité ou quelque chose)? Et les machines virtuelles "à processeur unique" fonctionnent sur ces machines? Mais je suppose que je me trompe sur ces hypothèses, car presque tous les processeurs sont multicœurs de nos jours ... mais j'examinerais peut-être un problème de multithreading (blocage).
la source
Comme il utilise 100% de CPU "la plupart du temps" et que cela prend 10 fois plus de temps (!) Avec Java 14, cela signifie que vous gaspillez 90% de votre CPU en Java 14.
Le fait de manquer d'espace de stockage peut le faire, car vous passez beaucoup de temps dans GC, mais vous semblez l'avoir exclu.
Je remarque que vous ajustez l'option de verrouillage biaisé et que cela fait une différence significative. Cela me dit que votre programme fait peut-être beaucoup de travail simultané dans plusieurs threads. Il est possible que votre programme ait un bogue de concurrence qui s'affiche en Java 14, mais pas en Java 10. Cela pourrait également expliquer pourquoi l'ajout d'un autre processeur le rend plus de deux fois plus rapide.
Les bogues de concurrence n'apparaissent souvent que lorsque vous n'avez pas de chance, et le déclencheur aurait pu être quelque chose, comme un changement dans l'organisation de la table de hachage, etc.
Tout d'abord, si cela est possible, recherchez les boucles qui pourraient être occupées à attendre au lieu de dormir.
Ensuite, exécutez un profileur en mode d'échantillonnage (jvisualvm fera l'affaire) et recherchez des méthodes qui prennent beaucoup plus de temps total qu'elles ne le devraient. Étant donné que vos performances sont diminuées d'un facteur 10, tout problème devrait vraiment sauter.
la source
TL; DR : C'est une régression OpenJDK.
Je n'ai pas excepté cela, mais j'ai pu reproduire le problème avec un simple bonjour:
J'ai utilisé ces deux fichiers batch:
main-1cpu.bat
, ce qui limite lejava
processus à un seul processeur:main-full.bat
, lejava
processus peut utiliser les deux CPU:(Les différences sont la
affinity
valeur et le nom du fichier journal. Je l'ai encapsulé pour une lecture plus facile mais l'habillage avec\
ne fonctionne probablement pas sous Windows.)Quelques mesures sur Windows 10 x64 dans VirtualBox (avec deux CPU):
Les tracelogs produits contiennent des pauses similaires que vous pouvez voir dans la question.
L'exécution
Main
sans tracelogs est plus rapide mais la différence est toujours visible entre la version à un seul processeur et la version à deux processeurs: ~ 4-7 secondes contre ~ 400 ms.J'ai envoyé ces résultats à la liste de diffusion hotspot-dev @ openjdk et les développeurs y ont confirmé que c'est quelque chose que le JDK pourrait mieux gérer . Vous pouvez également trouver des correctifs supposés dans le fil. Espérons que cela sera corrigé dans OpenJDK 15.
la source
Il s'agit d'un problème intéressant qui nécessiterait des efforts indéterminés pour le réduire, car de nombreuses permutations et combinaisons doivent être testées et les données collectées et rassemblées.
Il semble qu'il n'y ait pas eu de résolution à ce problème depuis un certain temps. Peut-être faudrait-il intensifier cela.
EDIT 2: Étant donné que "ThreadLocalHandshakes" est obsolète et que nous pouvons supposer que le verrouillage est contesté, suggérez d'essayer sans "UseBiasedLocking" pour accélérer, espérons-le, ce scénario.
Cependant, il existe quelques suggestions pour collecter plus de données et tenter d'isoler le problème.
J'ai personnellement été témoin de problèmes dans les versions qui ont à voir avec GC, redimensionnement de tas, problèmes avec les conteneurs virtualisés et ainsi de suite.
Il n'y a pas de réponse facile à cela, je pense, d'autant plus que cette question existe depuis un certain temps. Mais nous pouvons essayer, tout le meilleur et nous faire savoir quel est le résultat de certaines de ces étapes d'isolement.
EDIT 1: à partir de la question mise à jour, il semble être lié à un GC ou à un autre thread de service prenant en charge le noyau unique de manière non équitable (Thread-Local Handshakes)?
la source
Soyez prudent avec la journalisation pour ralentir les disques, cela ralentira votre application:
https://engineering.linkedin.com/blog/2016/02/iminating-large-jvm-gc-pauses-caused-by-background-io-traffic
Mais cela ne semble pas être la cause du problème car le CPU est toujours occupé et vous n'avez pas à attendre que tous les threads arrivent à un point sûr grâce à la négociation locale du thread: https: // openjdk. java.net/jeps/312
De plus, pas directement lié au problème que vous rencontrez, mais plus généralement si vous voulez essayer d'extraire plus de performances de votre matériel pour le temps de démarrage, jetez un œil à AppCDS (partage de données de classe):
https://blog.codefx.org/java/application-class-data-sharing/
la source