Sposoby na przyspieszenie startu aplikacji działających na Wirtualnej Maszynie Javy

Mateusz Kamiński
Head of Product Team & Technology Evangelist, Trener
Ikona kalendarza
25 czerwca 2025

Częstym problemem aplikacji napisanych w Javie jest wydłużony czas uruchamiania. Może być on spowodowany m. in. przez:

  • Dynamiczny class-loading – podczas startu następuje wczytywanie, walidacja i interpretacja bajtkodu klas. Im więcej zależności, bibliotek i frameworków znajduje się w projekcie, tym bardziej wpływa to na wydajność uruchomienia.
  • Inicjalizację stanu początkowego – statyczne pola w klasach muszą być zainicjalizowane, co dodatkowo wymaga synchronizacji.
  • Optymalizację bajtkodu do kodu natywnego – większość kodu jest optymalizowana przez mechanizm JIT (Just-In-Time Compiler), co powoduje tzw. efekt "rozgrzewania" się JVM po uruchomieniu.

Dotychczas systemy pisane w Javie były traktowane głównie jako klasyczne aplikacje serwerowe, gdzie liczy się przede wszystkim szybkość działania po uruchomieniu. Obecnie jednak Java coraz częściej znajduje zastosowanie w środowiskach, gdzie liczy się szybki czas startu – np. w usługach serverless takich jak AWS Lambda.

Ahead-of-Time Compiling z wykorzystaniem GraalVM

Jedną z najbardziej efektywnych metod przyspieszenia startu jest kompilacja aplikacji do kodu natywnego przy użyciu GraalVM. Zamiast pliku JAR zawierającego bajtkod, otrzymujemy natywny plik wykonywalny (np. .exe dla Windows), eliminując potrzebę rozgrzewania JVM.

Dodatkową zaletą jest możliwość przeniesienia inicjalizacji statycznych pól na etap kompilacji.

Aby to osiągnąć, zamiast klasycznego JDK z HotSpotem używamy GraalVM. Proces tworzenia natywnego obrazu oznacza kompilację do postaci gotowej do uruchomienia bez instalowania JRE.

Wadą podejścia AOT jest tzw. Closed-World Assumption, która ogranicza możliwość dynamicznego ładowania klas – mechanizmu często używanego niejawnie przez biblioteki i frameworki (np. poprzez refleksję). Na szczęście większość popularnych narzędzi udostępnia mechanizmy konfiguracji procesu kompilacji natywnej. W trudniejszych przypadkach możemy ręcznie rozszerzyć konfigurację.

Warto jednak pamiętać, że kod natywny może nie osiągać takiego poziomu optymalizacji jak kod działający w środowisku JIT, dlatego w przypadku aplikacji działających długoterminowo HotSpot z JIT-em nadal może być lepszym wyborem.

Nowość w Javie 24: Ahead-of-Time Class Loading & Linking

Dla tych, którzy nie chcą rezygnować ze standardowego JVM z JIT, Java 24 wprowadza możliwość tworzenia archiwów AOT, które znacząco przyspieszają start aplikacji bez pełnych ograniczeń GraalVM.

Archiwum AOT przyspiesza ładowanie klas i inicjalizację pól. W zależności od skali projektu może to znacznie zredukować czas uruchomienia. Mechanizm ten wymaga jednorazowego uruchomienia testowego w celu wygenerowania konfiguracji:

1$ java -XX:AOTMode=record -XX:AOTConfiguration=app.aotconf -cp app.jar com.example.App ...
2$ java -XX:AOTMode=create -XX:AOTConfiguration=app.aotconf -XX:AOTCache=app.aot -cp app.jar

Następnie przy kolejnych uruchomieniach wykorzystujemy wygenerowane archiwum:

1$ java -XX:AOTCache=app.aot -cp app.jar com.example.App ...

Taki plik AOT może być tworzony już na etapie budowania kontenera Dockerowego. Co ważne, mechanizm ten nie wymaga zmian w kodzie źródłowym i jest zgodny z zasadą Write Once, Run Anywhere.

Class Data Sharing (CDS)

Jeśli nie możemy korzystać z najnowszych wersji JDK, alternatywą jest mechanizm Class Data Sharing (CDS), który pozwala współdzielić informacje o klasach między uruchomieniami aplikacji.

CDS działa automatycznie dla klas z JDK, ale możemy go rozszerzyć o własne typy:

  1. Wygeneruj listę klas:

    1java -XX:+DumpLoadedClassList=classlist.txt -cp app.jar com.example.App
  2. Wygeneruj archiwum:

    1java -Xshare:dump -XX:SharedClassListFile=classlist.txt -cp app.jar
  3. Wykorzystaj archiwum przy uruchomieniu:

    1java -Xshare:auto -XX:SharedArchiveFile=generated_archive.jsa -cp app.jar com.example.App

Od JDK 13 można skrócić ten proces za pomocą flagi -XX:ArchiveClassesAtExit.

Coordinated Restore at Checkpoint (CRaC)

Mechanizm CRaC przypomina hibernację znaną z systemów operacyjnych – zapisuje stan JVM i pozwala wznowić działanie bez ponownego rozgrzewania.

Wymaga jednak JDK ze wsparciem CRaC oraz systemu Linux obsługującego CRIU. Przykładowe implementacje:

W wielu przypadkach konieczne będzie dodanie kodu obsługującego ponowne inicjalizacje (np. zamykanie i otwieranie plików czy gniazd sieciowych). Współczesne frameworki oferują już częściowe wsparcie dla CRaC.

Wirtualne wątki

Wirtualne wątki, dostępne od Javy 21, mogą także przyspieszyć uruchamianie aplikacji – szczególnie w zakresie kodu użytkownika. Klasyczne pule wątków bywają kosztowne, zwłaszcza jeśli są inicjalizowane jako statyczne.

Możemy je zastąpić:

1Executors.newVirtualThreadPerTaskExecutor();

lub

1Executors.newThreadPerTaskExecutor(myVirtualThreadFactory);

Warto unikać wirtualnych wątków w przypadku zadań długoterminowych. Idealnie sprawdzają się przy zadaniach krótkich lub intensywnie wykorzystujących operacje I/O.

JIT Server

Jeśli uruchamiamy wiele instancji tej samej aplikacji (np. w środowisku chmurowym lub Kubernetesie), warto rozważyć wykorzystanie serwera JIT – miejsca, gdzie gromadzony jest zoptymalizowany kod natywny.

Technologia ta dostępna jest w środowisku OpenJ9 lub gotowych dystrybucjach, np.:

JIT Server jest szczególnie przydatny tam, gdzie aplikacje często się restartują lub podlegają automatycznemu skalowaniu.

Podsumowanie

W artykule przedstawiono różne mechanizmy pozwalające przyspieszyć start aplikacji działających na JVM. Wybór konkretnej metody zależy od kontekstu i ograniczeń projektu.

Niezależnie od zastosowanej technologii, warto pamiętać o ogólnych zasadach: opóźnianie inicjalizacji, leniwe ładowanie danych oraz unikanie kosztownych operacji w metodzie main() to dobre praktyki poprawiające czas uruchomienia.


Chcesz dowiedzieć się więcej o optymalizacji aplikacji JVM i działaniu samej maszyny wirtualnej? Sprawdź nasze szkolenie: 👉 Wydajność aplikacji na platformie Java – Sages

Przeczytaj także

Ikona kalendarza

23 czerwiec

Nawet 90% dofinansowania na szkolenia - rusza nowy konkurs PARP

Wystartował konkurs PARP z rekordową pulą 361 mln zł na rozwój kompetencji przyszłości dla przedsiębiorców i m.in. uczelni wyższych. ...

Ikona kalendarza

30 maj

Projektowanie systemów rozproszonych i skalowalnych – podejście architektoniczne i praktyczne wyzwania

W dobie rosnących oczekiwań użytkowników i gwałtownego wzrostu wolumenów danych, projektowanie systemów informatycznych coraz częście...

Ikona kalendarza

7 maj

Aidapta – rewolucyjne rozwiązanie w dostępności cyfrowej oparte na AI

Aidapta, wcześniej znana jako WCAG AI, to zaawansowane narzędzie, które automatyzuje proces dostosowywania treści cyfrowych do wy...