Java 12 - nowe funkcje
Java 12 weszła w fazę General Availability. Po wielu nowościach, dostarczonych wraz z Javą 11, tym razem mamy do czynienia z nieco skromniejszym wydaniem, które nie jest LTS-em. Zobaczmy, co oferuje programistom ta wersja Javy.
To wydanie zawiera zaledwie 8 JEP (czyli JDK Enhancement Proposal), z których najstarsze to 5-letnie propozycje.
Wyrażenie Switch
Chyba najgorętsza nowość, o której mówi się w internecie. Switch od teraz jest już wyrażeniem. Oprócz tego pojawiła się nowa składnia, z której można skorzystać w trybie preview (dzięki przełącznikowi --enable-preview). To, co jeszcze w Javie 11 można było zapisać tak:
int numLetters;
switch (day) {
case MONDAY:
case FRIDAY:
case SUNDAY:
numLetters = 6;
break;
case TUESDAY:
numLetters = 7;
break;
case THURSDAY:
case SATURDAY:
numLetters = 8;
break;
case WEDNESDAY:
numLetters = 9;
break;
}
od teraz można wyrazić w ten sposób:
int numLetters = switch (day) {
case MONDAY, FRIDAY, SUNDAY -> 6;
case TUESDAY -> 7;
case THURSDAY, SATURDAY -> 8;
case WEDNESDAY -> 9;
};
Dużo bardziej elegancko i w stylu, który przyjęło wiele innych współczesnych języków programowania. Nie trzeba używać nowej składni case L ->
, stara nadal działa, więc kod będzie wstecznie kompatybilny. Należy jednak pamiętać, że w nowej składni nie obowiązuje dopasowanie wartości do skutku, a w przypadku, gdy chcemy użyć tej samej operacji dla kilku wartości, trzeba użyć takiej składni: case L1, L2, L3… ->
.
Odśmiecanie pamięci
Garbage Collector Shenandoah (JEP 189)
Zaczynamy tu od najstarszej propozycji, nad którą prace zaczęły się na początku 2014 roku, czyli Garbage Collectora Shenandoah (JEP 189). Jest on eksperymentalnym GC, którego celem jest zmniejszenie czasu przestoju spowodowanego odśmiecaniem (GC pause). Czas ten zmniejsza się głównie przez równoległe przeprowadzanie ewakuacji obiektów, czego nie potrafią np. G1 czy CMS. Nowy algorytm nie jest zależny od rozmiaru sterty, więc będzie miał taki sam czas przestoju dla sterty 100MB, jak i 100GB.
Należy powiedzieć, że Shenandoah nie jest lekiem na całe zło i nie ma na celu zastąpienia innych GC. Słabością nowego rozwiązania jest przepustowość i wykorzystanie pamięci. Twórcy mają nadzieję, że przyjmie się w zastosowaniach, gdzie potrzebne jest niskie opóźnienie, które nie jest możliwe do uzyskania przy długich przestojach GC.
Możecie go wypróbować, ustawiając następujące opcje JVM XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC
. Jeżeli chcecie dowiedzieć się, jak działa ten GC pod spodem, to jest dostępny artykuł na ten temat.
Optymalizacja czasu przestoju GC w G1
G1 umożliwia ustawianie docelowej pauzy i zapewnia huerestykę, dzięki której może sam określić zakres pracy, który wykona w czasie odśmiecania, by w tym czasie się zmieścić. Jednak heurystyka nie jest idealna i często może niedoszacować ilość pracy do wykonania, co skutkuje przekroczeniem limitu czasu pauzy.
Tu przychodzi z pomocą JEP 344, czyli możliwość przerwania odśmiecania. W przypadku, gdy sytuacja z przekroczeniem ustawionej pauzy się powtarza, G1 podzieli regiony, które chce odśmiecić, na dwie części: obowiązkową i opcjonalną. Obowiązkowa będzie zawierała młode regiony i zostanie dopełniona odpowiednimi starszymi regionami dla odpowiedniej efektywności. Część opcjonalna będzie zawierać tylko stare regiony i zostanie dalej podzielona na podczęści. Podczęści zostaną odśmiecone, jeżeli będzie na to czas, a G1 będzie optymalizować swoje działanie, aż wszystko wróci do normy. Jeżeli znów pojawią się anomalie, cały proces się powtórzy.
Szybsze zwracanie do systemu przez G1 nieużywanej pamięci, czyli JEP 346
W tym momencie pamięć do systemu jest zwracana tylko przy pełnym GC albo przy równoległym cyklu. G1 bardzo mocno unika wykonywania pełnego odśmiecania, a równoległy cykl jest wywoływany na podstawie aktywności alokacji, jak również zajętości sterty. Było wiele scenariuszy, w których pamięć w ogóle nie była zwracana do systemu, dopóki nie zmusiło się do tego aplikacji. Rozwiązaniem, na jakie wpadł tu zespół pracujący nad Javą, jest cykliczne uruchamianie równoległego lub pełnego cyklu GC w czasie, gdy aplikacja nie jest obciążona. Są dwa warunki, by tak się stało: musi upłynąć przynajmniej G1PeriodicGCInterval
ms od ostatniego cyklu i funkcja sprawdzająca średnie obciążenie w ciągu ostatniej minuty getloadavg()
musi zwrócić mniej niż G1PeriodicGCSystemLoadThreshold
(o ile taki próg jest ustawiony).
Szybciej i bardziej spójnie
Od tej pory archiwum class data-sharing (CDS) będzie generowane automatycznie. Zapewnia to JEP 341. CDS pomaga w szybszym starcie aplikacji. Dla HelloWorld to aż 32%. Jest więc o co grać, dlatego w czasie kompilacji z automatu zostanie dodane archiwum z CDS do wyprodukowanego obrazu.
W JVM zostało dodane API do modelowania ładowalnych stałych (JEP 334), które są zorganizowane w pule stałych (constant pool). Stałych z tych puli można używać w dynamiczny sposób np. w invokedynamic. Wtedy jednak stałe te muszą zostać zmapowane na żywe klasy javowe, co może polegać na systemie ładowania klas. Programiści JDK pomyśleli, że dużym ułatwieniem byłoby opisywanie ładowalnych stałych w czysto nominalnej formie, niezwiązanej bezpośrednio z faktycznymi klasami. Szczegóły tego API można zobaczyć tutaj.
Do tej pory JDK miało… dwa porty na 64-bitowy ARM. Jeden z nich to arm64, drugi to aarch64. Zdecydowano porzucić arm64 i pozostawić tylko aarch64 (JEP 340).
Ostatnią nowością jest dodanie dla samego OpenJDK zestawu mikrobenchmarków. Bazują one na Java Microbenchmark Harness i będą służyły do śledzenia regresji wydajności w tej i przyszłych wersjach JDK.
Warto przejść?
Java 12 nie jest wersją LTS, więc już za pół roku zastąpi ją Java 13. W większych projektach może być szkoda czasu na migrację, a w dodatku w stosunku do Javy 11 nie ma wcale dużo nowości. Być może jeżeli macie duże problemy z GC pause i używacie G1, to warto zobaczyć czy Java 12 w jakikolwiek sposób Wam pomaga. Podobnie mogą pomyśleć ludzie, którzy odbijają się od ograniczeń dotyczących puli stałych. W przeciwnym razie w te same rezultaty można uzyskać, używając Javy 11.