Wydajność Java 8 vs Java 11

Język programowania Java rozwija się bardzo dynamicznie. Twórcy platformy Java wprowadzają wiele udogodnień dzięki czemu jakość i czytelność kodu znacznie wzrasta, a proporcjonalnie zmniejszają się koszty tworzenia i utrzymania aplikacji. Z każdą kolejną publikacją wersji Javy często wiążą się zmiany, które nie widać gołym okiem, a wpływają znacząco na wydajność działania aplikacji.

W tym artykule znajdziesz odpowiedzi na poniższe pytania:

  • Dlaczego warto aktualizować wersje Java?
  • Jakie zostały wprowadzone optymalizacje do Javy 11?
  • Czy zmiany w domyślnych bibliotekach mają wpływ na wydajność aplikacji?
  • Czy warto migrować stare aplikacje napisane przed laty do nowszych wersji Javy?

Garbage Collector

Od wersji Java 8 do wersji Java 11 wprowadzono sporo zmian w oczyszczaniu pamięci. Od Java 9 domyślnym modułem Garbage Collector jest algorytm G1 Garbage Collector. Zmiana została zatwierdzona w JEP 248. Oprócz ustawienia jako domyślny moduł, wprowadzono kilka poprawek, które mają na celu przyśpieszenia jego pracy. Więcej informacji na temat działania i wprowadzonych poprawek można znaleźć w „G1 Garbage Collector„.

Pojawiły się dwa nowe moduły. Moduł Epsilon Garbage Collector, który został wprowadzony w JEP 318 oraz moduł Z Garbage Collector wprowadzony w JEP 333.

Moduł Epsilon Garbage Collector obsługuje alokację pamięci, ale nie implementuje żadnego mechanizmu odzyskiwania pamięci. Po wyczerpaniu dostępnej sterty maszyna JVM zostanie zamknięta. Moduł jest przydatny w przypadku usług krótkotrwałych, niezajmujących dużej przestrzeni i aplikacji, o dość niskim priorytecie.

Celem powstania modułu Z Garbage Collector jest osiągnięcie niskiego opóźnienia, poprzez unikania zatrzymywania wątków aplikacyjnych. Twórcy założyli za cel, że czas pauzy nie przekroczy 10 milisekund, a redukcja przepustowości aplikacji osiągnie nie więcej niż 15% w porównaniu do korzystania z G1Garbage Collector. Moduł Z Garbage Collector jest odpowiedni dla aplikacji, które wymagają małych opóźnień i używają sterty liczącej w terabajtach.

Kompilator JIT

O ile w komponencie Garbage Collector możemy zauważyć sporo zmian, to w komponencie JIT niewiele się zmieniło. Zmiany możemy zauważyć dopiero w momencie, gdy podejmiemy się zmiany kompilatora na inny. W Javie wersji 9 o kodzie JEP 243 opracowano interfejs kompilatora JVM, zwany JVMCI. Interfejs umożliwia wprowadzenie do JVM kompilatora napisanego w języku Java. Wraz z wydaniem udostępniono kompilator Graal oraz kompilator AOT, który służy do skompilowania klas już przed uruchomieniem maszyny wirtualnej. Oprócz wydania nowych kompilatorów wprowadzono segmentacje pamięci podręcznej kodu w JEP 197. Celem jest podział sterty kodu na odrębne segmenty: niemetodowy, profilowany i nieprofilowany. Więcej informacji w „Kompilator Just-in-Time„.

Biblioteki w JDK

Oprócz samych zmian w wirtualnej maszynie Javy, twórcy wprowadzili drobne poprawki
do podstawowych bibliotek załączonych w JDK. Do najważniejszych zmian należy:

  • JEP 254: Compact Strings – zmiany wprowadzone w wersji 9, które obejmują komponent core-libs/java.lang. Wprowadzone ulepszenie zmienia wewnętrzną reprezentację klasy String i klas pochodnych. Dokładnie mówiąc to pojedynczy znak z klasy String będzie można reprezentować jako jeden bajt zamiast dwóch bajtów. Skutkować będzie to zmniejszeniem ilości miejsca wymaganego do przechowywania ciągu znaków nawet o połowę. Główny cel twórców to poprawa wydajności. Optymalizacja będzie miała miejsce tylko dla znaków o kodowaniu Latin-1. W kodowaniu Latin-1 można przedstawić większość języków wychodzących z alfabetu łacińskiego.
  • JEP 310: Application Class-Data Sharing – zmiany wprowadzone w wersji 10. Celem projektu jest rozszerzenie istniejącej funkcji Class-Data Sharing, która została wprowadzona w 5 wydaniu Javy. Wprowadzając rozszerzenie, będzie możliwe umieszczenie klas aplikacji we współużytkowanym archiwum. A co za tym idzie, to przyśpieszenie czasu uruchomienia aplikacji i oszczędność miejsca w pamięci używanej przez metadane klasy Java. Biorąc pod uwagę wytwarzane w korporacjach aplikacje produkcyjne może mieć to znaczny wpływ na wydajność.
  • JEP 312: Thread-Local Handshakes – zmiana wprowadzona w wersji 10. JEP dotyczy środowiska uruchomieniowego i nie wpływa na interfejs API. Celem projektu jest umożliwienie wykonania wywołania zwrotnego w wątkach bez wykonywania globalnego punktu bezpiecznego maszyny wirtualnej. Pozwoli to na osiągnięcie mniejszego opóźnienia poprzez zmniejszenie liczby globalnych punktów bezpieczeństwa.
  • JEP 220: Modular Run-Time Images – koncepcja modularnych obrazów środowiska uruchomieniowego została wprowadzona w wersji 9. Zmiany pozwalają na generowanie niestandardowych obrazów JRE, które zawierają tylko moduły wymagane do uruchomienia i działania aplikacji. Wprowadzenie modułów ma na celu poprawić wydajność, bezpieczeństwo i oszczędność pamięci.
  • JEP 193: Variable Handles i JEP 266: More Concurrency Updates – zmiany wprowadzone wraz z wersją 9 i obejmują komponenty core-libs/java.lang i core-libs / java.util.concurrent. JEP 193 definiuje standardowe sposoby do wywołania odpowiedników dla zmiennych z pakietów java.util.concurrent.atomic i sun.misc. Wprowadzają ulepszenia w zakresie współbieżności i równoległości w aplikacjach. Założonymi celami są: bezpieczeństwo, integralność, wydajność i użyteczność.
  • JEP 269: Convenience Factory Methods for Collections – zmiany wprowadzone w wersji 9 i obejmuje komponent core-libs / java.util:collections. Definiuje API, które ułatwi tworzenie instancji kolekcji z małą liczbą elementów. Za pomocą wprowadzonego API, można utworzyć kompaktowe i niemodyfikowalne kolekcje. Jak sami twórcy wspomnieli, celem nie jest polepszenie wydajności, ale użycie może wpłynąć na wykonywany kod.
  • JEP 285: Spin-Wait Hints – zmiana wprowadzona w wersji 9, które obejmuje komponent core-libs/java.lang. Definiuje interfejs API, który pozwala zasugerować systemowi, że znajduje się w pętli spin. Pętla spinowa lub technika zajętego oczekiwania to pętla, w której określony warunek jest wielokrotnie sprawdzany. Przykładem takiej pętli jest kod znajdujący się na rysunku 13. Za pomocą tego mechanizmu w niektórych procesorach można poprawić wydajność wykonywania kodu.
  • JEP 321: HTTP Client – zmiana wprowadzona w wersji 11 i obejmuje komponent core-libs/java.net. Zapewnia nowe API klienta http, które oprócz zaimplementowania http/2 i websocket postawiono za cel przystosowanie się do aktualnych wymagań bezpieczeństwa i wydajności. Warunkiem wprowadzenia tej zmiany, było spełnienie celu, który wymagał od nowego API, aby zużycie pamięci było równe lub niższe od istniejącego rozwiązania. 
  • JEP 332: Transport Layer Security (TLS) 1.3 – zmiany wprowadzone w wersji 11 dla komponentu security-libs / javax.net.ssl. Celem było zaimplementowanie Transport Layer Security w wersji 1.3. Skutkuje to poprawą bezpieczeństwa i wydajności w porównaniu z poprzednimi wersjami.

Podsumowanie

W artykule wymieniłem jedne z najważniejszych zmian, które mogą wpłynąć na wydajność działania aplikacji. Jesteś ciekawy jakie faktycznie wyniki możemy otrzymać migrując z Java 8 do Java 11? Zapraszam do przeczytania pracy „Analiza i ocena wydajności aplikacji w języku Java w wersji 8 i 11” oraz przejrzenie kodu w projekcie „jmh-benchmarks” na github’ie 😉.

Ciekawe artykuły

  1. N. Samoylov, M. Sanaulla, Using application class-data sharing, W Java 11 Cookbook – Second Edition, Packt, https://subscription.packtpub.com/book/application_development/9781789132359/1/ch01lvl1sec16/using-application-class-data-sharing
  2. M. Balci, Spin-Wait Hints in Java, https://metebalci.com/blog/spin-wait-hints-in-java/

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *