У нас есть клиентское приложение (с 10+ лет разработки). Его JDK был недавно обновлен с OpenJDK 11 до OpenJDK 14. На однопроцессорных (с отключенной гиперпоточностью) настройках Windows 10 (и на машинах VirtualBox с одним доступным ЦП) приложение запускается довольно медленно по сравнению с Java 11. Кроме того, большую часть времени оно использует 100% ЦП. Мы могли бы также воспроизвести проблему с установкой привязки процессора только к одному CPU ( c:\windows\system32\cmd.exe /C start /affinity 1 ...
).
Некоторые измерения с запуском приложения и выполнением запроса с минимальным ручным взаимодействием на моей машине VirtualBox:
- OpenJDK 11.0.2: 36 секунд
- OpenJDK 13.0.2: ~ 1,5 минуты
- OpenJDK 13.0.2 с
-XX:-UseBiasedLocking
: 46 секунд - OpenJDK 13.0.2 с
-XX:-ThreadLocalHandshakes
: 40 секунд - OpenJDK 14: 5-6 минут
- OpenJDK 14 с
-XX:-UseBiasedLocking
: 3-3,5 минуты - OpenJDK 15 EA Build 20: ~ 4,5 минуты
Только использованный JDK (и упомянутые опции) был изменен. ( -XX:-ThreadLocalHandshakes
недоступно в Java 14.)
Мы попытались записать, что делает JDK 14 -Xlog:all=debug:file=app.txt:uptime,tid,level,tags:filecount=50
.
Подсчет строк журнала для каждой секунды кажется довольно плавным с 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
С другой стороны, OpenJDK 14, кажется, имеет интересные периоды тишины:
$ 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
Итак, что происходит между секундами 1-4, 7-10 и 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
...
Вторая пауза чуть позже:
...
[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
...
Затем третий:
...
[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)
...
Следующие две строки кажутся интересными:
[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
Это нормально, что эти рукопожатия заняли 5,5 и 6,8 секунды?
Я испытал такое же замедление (и похожие журналы) с демонстрационным приложением update4j (которое совершенно не связано с нашим приложением), работающим с этой командой:
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
Что я должен искать, чтобы снова ускорить работу нашего приложения на однопроцессорных установках Windows 10? Можно ли это исправить, изменив что-то в нашем приложении или добавив аргументы JVM?
Это ошибка JDK, я должен сообщить об этом?
обновление 2020-04-25:
Насколько я вижу, файлы журналов также содержат журналы GC. Это первые журналы 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)
...
К сожалению, это не похоже на связь, поскольку начинается после третьей паузы.
обновление 2020-04-26:
В OpenJDK 14 приложение использует 100% ЦП на моей (однопроцессорной) машине VirtualBox (работающей на ЦП i7-6600U). Виртуальная машина имеет 3,5 ГБ оперативной памяти. По словам диспетчера задач 40% + является свободным, а активность диска составляет 0% (я думаю, это означает, что нет подкачки). Добавление еще одного ЦП к виртуальной машине (и активация гиперпоточности для физических машин) делает приложение снова достаточно быстрым. Мне просто интересно, был ли это преднамеренный компромисс во время разработки JDK с потерей производительности на (редких) однопроцессорных машинах, чтобы ускорить JVM на многоядерных / гиперпоточных процессорах?
-Xlog:all=debug
ведение журнала GC? Это было бы мое первое предположение для любых пауз.Ответы:
Из моего опыта проблемы с производительностью JDK связаны в основном с одним из следующих:
Если вы просто используете конфигурацию JVM по умолчанию, начиная с OpenJDK11, возможно, вам следует установить некоторые из наиболее заметных опций на фиксированные значения, такие как GC, размер кучи и т. Д.
Возможно, какой-нибудь графический инструмент анализа поможет отследить вашу проблему. Как Retrace, AppDynamics или FlightRecorder и тому подобное. Они дают больше общего обзора общего состояния кучи, циклов gc, оперативной памяти, потоков, загрузки ЦП и т. Д. В определенный момент времени, чем могут предоставить файлы журнала.
Правильно ли я понимаю, что ваше приложение записывает около 30710 строк в журнал в течение первой секунды работы (под OpenJDK11)? Почему он «только» пишет о 7k строк в OpenJDK14 в первую секунду? Мне кажется, это огромная разница для приложения, которое только что запущено на разных JVM ... Вы уверены, что, например, в журнал не помещено большое количество стековых трасс Exception?
Другие числа иногда даже выше, так что, возможно, замедления связаны с регистрацией исключений? Или даже обмен, если RAM становится мало?
На самом деле, я думаю, что если приложение ничего не записывает в журнал, это является признаком бесперебойной работы без проблем (если только в это время оно полностью не заморожено). То, что происходит со секунд 12-22 (в случае OpenJDK14 здесь), это то, что беспокоит меня больше ... зарегистрированные строки проходят через крышу ... почему ?
И после этого ведение журнала снижается до минимальных значений, примерно 1-2 тыс. Строк ... в чем причина этого ? (ну, может быть, это gc включается в секунду 22 и делает tabula rasa, который разрешает некоторые вещи ...?)
Другое дело может быть ваше утверждение о «однопроцессорных» машинах. Означает ли это также «одно ядро» (ИДК, может, ваше программное обеспечение настроено на устаревшее оборудование или что-то в этом роде)? И виртуальные машины с одним процессором работают на этих машинах? Но я предполагаю, что я ошибаюсь в этих предположениях, так как в настоящее время почти все процессоры являются многоядерными ... но я бы мог заняться проблемой многопоточности (взаимоблокировки), возможно.
источник
Поскольку в большинстве случаев он использует 100% ЦП, а в Java 14 это занимает в 10 раз больше времени (!), Это означает, что в Java 14 вы тратите 90% своего ЦП.
Это может привести к нехватке места в куче , поскольку вы проводите много времени в GC, но, похоже, вы это исключили.
Я заметил, что вы настраиваете смещенную блокировку, и она имеет существенное значение. Это говорит мне о том, что, возможно, ваша программа выполняет много параллельной работы в нескольких потоках. Возможно, в вашей программе есть ошибка параллелизма, которая появляется в Java 14, но не в Java 10. Это также может объяснить, почему добавление другого CPU делает его более чем в два раза быстрее.
Ошибки параллелизма часто появляются только тогда, когда вам не повезло, и триггером действительно могло быть что угодно, например, изменение организации hashmap и т. Д.
Во-первых, если это возможно, проверьте наличие петель, которые могут быть заняты, а не спать.
Затем запустите профилировщик в режиме выборки (jvisualvm сделает) и найдите методы, которые занимают гораздо больший% общего времени, чем следовало бы. Поскольку ваша производительность снижается в 10 раз, любые проблемы там действительно должны выпадать.
источник
TL; DR : это регрессия OpenJDK.
Я не сделал ничего, кроме этого, но я мог воспроизвести проблему с простым привет миром:
Я использовал эти два командных файла:
main-1cpu.bat
, который ограничиваетjava
процесс только одним процессором:main-full.bat
, Тоjava
процесс может использовать оба CPU:(Различия заключаются в
affinity
значении и имени файла журнала. Я обернул его для облегчения чтения, но,\
вероятно, в Windows это не работает.)Несколько измерений на Windows 10 x64 в VirtualBox (с двумя процессорами):
Полученные следы содержат похожие паузы, которые вы можете увидеть в вопросе.
Работа
Main
без трассировки ведется быстрее, но все же можно увидеть разницу между версией с одним и двумя процессорами: ~ 4-7 секунд против ~ 400 мс.Я отправил эти результаты в список рассылки hotspot-dev @ openjdk, и разработчики подтвердили, что JDK справится с этим лучше . Вы также можете найти предполагаемые исправления в теме. Надеюсь, это будет исправлено в OpenJDK 15.
источник
Это интересный вопрос, и для его сужения потребуются неопределенные усилия, так как существует множество перестановок и комбинаций, которые необходимо опробовать, а также собрать и сопоставить данные.
Кажется, что в течение некоторого времени не было никакого решения. Возможно, это должно быть обострено.
РЕДАКТИРОВАТЬ 2: Поскольку «ThreadLocalHandshakes» устарела, и мы можем предположить, что блокировка считается допустимой, предложите попробовать без «UseBiasedLocking», чтобы, надеюсь, ускорить этот сценарий.
Однако есть некоторые предложения, чтобы собрать больше данных и попытаться изолировать проблему.
Я лично был свидетелем проблем в версиях, связанных с GC, изменением размера кучи, проблемами с виртуализированными контейнерами и так далее.
Я думаю, что нет простого ответа на этот вопрос, тем более что этот вопрос существует уже некоторое время. Но мы можем попробовать все возможное и дать нам знать, что является результатом некоторых из этих шагов изоляции.
РЕДАКТИРОВАТЬ 1: из обновленного вопроса, это, кажется, связано с GC или другим потоком службы, принимая на себя одно ядро неравномерно (Thread-Local Handshakes)?
источник
Будьте осторожны с регистрацией на медленных дисках, это замедлит ваше приложение:
https://engineering.linkedin.com/blog/2016/02/eliminating-large-jvm-gc-pauses-caused-by-background-io-traffic
Но вряд ли это является причиной проблемы, так как процессор все еще занят, и вам не нужно ждать, пока все потоки достигнут безопасной точки, благодаря локальному рукопожатию: https: // openjdk. java.net/jeps/312
Также не имеет прямого отношения к вашей проблеме, но в более общем плане, если вы хотите попытаться увеличить производительность вашего оборудования во время запуска, взгляните на AppCDS (класс обмена данными):
https://blog.codefx.org/java/application-class-data-sharing/
источник