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:-ThreadLocalHandshakes
ist 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.
-Xlog:all=debug
die GC-Protokollierung? Das wäre meine erste Vermutung für irgendwelche Pausen.Antworten:
Aus meiner Erfahrung hängen Leistungsprobleme mit JDKs hauptsächlich mit einem der folgenden Probleme zusammen:
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.
quelle
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.
quelle
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.
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).
quelle
TL; DR : Es ist eine OpenJDK-Regression.
Ich habe das nicht ausgenommen, aber ich konnte das Problem mit einer einfachen Hallo-Welt reproduzieren:
Ich habe diese beiden Batch-Dateien verwendet:
main-1cpu.bat
, was denjava
Prozess auf nur eine CPU beschränkt:main-full.bat
kann derjava
Prozess beide CPUs verwenden:(Die Unterschiede sind der
affinity
Wert 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):
Die erstellten Tracelogs enthalten ähnliche Pausen, die Sie in der Frage sehen können.
Das Ausführen
Main
ohne 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.
quelle
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/
quelle