Stable
467: Markdown Documentation Comments
JEP 467 wprowadza możliwość pisania komentarzy dokumentacyjnych JavaDoc za pomocą Markdown, oprócz dotychczasowego formatu HTML i tagów JavaDoc. Ułatwia to tworzenie i czytanie dokumentacji umieszczanej bezpośrednio w kodzie źródłowym.
Jaki jest problem?: Obecne komentarze dokumentacyjne JavaDoc korzystają z HTML i specyficznych tagów JavaDoc, co jest trudne do pisania i czytania. HTML ma to do siebie, że bardzo „zanieczyszcza” treść, co zniechęca programistów do tworzenia dobrze sformatowanej dokumentacji. Dodatkowo, tagi do dokumentowania JavaDoc są mniej znane i często wymagają konsultacji z ich własną dokumentacją (co jak na to spojrzeć robi robi się nieco meta).
Solution: JEP 467 wprowadza wsparcie dla Markdown w komentarzach dokumentacyjnych, co umożliwia bardziej zwięzłe i czytelne formatowanie dokumentacji. Markdown jest obecnie standardem wśród języków znaczników używanych przez programistów, a na jego popularyzacje wpłynął min. GitHub, więc składnia po prostu jest szeroko używana i znana (ciekawostka – wszystkie edycje JVM Weekly powstają oryginalnie właśnie w Markdownie). Dzięki temu programiści mogą pisać dokumentację szybciej i z mniejszym wysiłkiem, nadal mając możliwość korzystania z HTML i tagów JavaDoc tam, gdzie jest to konieczne (dodatkowo – wiele popularnych parserów Markdown jest kompatybilnych ze wstawkami z HTML).
Przykład kodu:
Tradycyjny komentarz JavaDoc:
/**
* Returns a <b>hash code</b> value for the object.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Must consistently return the same integer for the same object,
* provided no information used in {@code equals} comparisons is modified.
* <li>If two objects are equal according to the {@link #equals(Object)} method,
* calling {@code hashCode} on each must produce the same result.
* <li>It is not required that unequal objects produce distinct integers,
* but this improves the performance of hash tables.
* </ul>
*
* @implSpec
* The {@code hashCode} method defined by class {@code Object} returns distinct
* integers for distinct objects as far as is practical.
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/
Ten sam komentarz Markdown:
/// Returns a **hash code** value for the object.
///
/// The general contract of `hashCode` is:
///
/// - Must consistently return the same integer for the same object,
/// provided no information used in `equals` comparisons is modified.
/// - If two objects are equal according to the [equals][#equals(Object)] method,
/// calling `hashCode` on each must produce the same result.
/// - It is not required that unequal objects produce distinct integers,
/// but this improves the performance of hash tables.
///
/// @implSpec
/// The `hashCode` method defined by class `Object` returns distinct
/// integers for distinct objects as far as is practical.
///
/// @return a hash code value for this object.
/// @see java.lang.Object#equals(java.lang.Object)
/// @see java.lang.System#identityHashCode
471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal
JEP 471 ma na celu deprekację metod dostępu do pamięci w klasie sun.misc.Unsafe
z zamiarem ich usunięcia w przyszłych wydaniach JDK. Propozycja ta ma na celu zachęcenie programistów do migracji na obsługiwane zamienniki, aby aplikacje mogły płynnie przechodzić na nowsze wersje JDK.
Jaki jest problem?: Metody dostępu do pamięci pochodzące z sun.misc.Unsafe
są niebezpieczne i mogą prowadzić do nieokreślonego zachowania, w tym wywalenia się całego JVM. Pomimo że nie były one przeznaczone do szerokiego użycia, stały się popularne wśród programistów, którzy szukali większej wydajności i mocy niż oferowane przez standardowe API. Brak kontroli bezpieczeństwa przed ich użyciem może prowadzić do błędów i awarii aplikacji.
Solution: JEP 471 wprowadza deprecjację metod dostępu do pamięci w sun.misc.Unsafe
, zachęcając programistów do przejścia na bezpieczne i wydajne zamienniki: VarHandle
(JEP 193) i MemorySegment
(JEP 454). Propozycja przewiduje stopniowe usuwanie dostępu do metod z sun.misc.Unsafe
w kilku fazach, zaczynając od ostrzeżeń kompilacji, poprzez ostrzeżenia w czasie wykonania, aż po faktyczne usunięcie metod. Dodano nowe opcje wiersza poleceń, takie jak --sun-misc-unsafe-memory-access={allow|warn|debug|deny}
, umożliwiające deweloperom granularne testowanie ocenę wpływu deprecjacji metod dostępu do pamięci.
Przykład kodu:
Przykład kodu z użyciem sun.misc.Unsafe
:
class Foo {
private static final Unsafe UNSAFE = ...;
private static final long X_OFFSET;
static {
try {
X_OFFSET = UNSAFE.objectFieldOffset(Foo.class.getDeclaredField("x"));
} catch (Exception ex) { throw new AssertionError(ex); }
}
private int x;
public boolean tryToDoubleAtomically() {
int oldValue = x;
return UNSAFE.compareAndSwapInt(this, X_OFFSET, oldValue, oldValue * 2);
}
}
Przykład kodu z użyciem VarHandle
:
class Foo {
private static final VarHandle X_VH;
static {
try {
X_VH = MethodHandles.lookup().findVarHandle(Foo.class, "x", int.class);
} catch (Exception ex) { throw new AssertionError(ex); }
}
private int x;
public boolean tryAtomicallyDoubleX() {
int oldValue = x;
return X_VH.compareAndSet(this, oldValue, oldValue * 2);
}
}
474: ZGC: Generational Mode by Default
JEP 474 wprowadza zmianę domyślnego trybu działania Z Garbage Collector (ZGC) na tryb generacyjny. Dotychczasowy tryb niegeneracyjny zostanie oznaczony jako przestarzały i planowany do usunięcia w przyszłych wydaniach JDK.
Jaki jest problem?: Utrzymywanie zarówno generacyjnego, jak i niegeneracyjnego trybu ZGC spowalnia rozwój nowych funkcji. Tryb generacyjny ZGC jest uważany za lepsze rozwiązanie dla większości przypadków użycia, co czyni tryb niegeneracyjny zbędnym.
Jak proposal rozwiązuje ten problem?: Propozycja zmienia domyślną wartość opcji ZGenerational
na true
, włączając tym samym tryb generacyjny ZGC jako domyślny. Po wprowadzeniu tych zmian, uruchomienie JVM z opcją -XX:+UseZGC
będzie domyślnie uruchamiało tryb generacyjny ZGC. Tryb niegeneracyjny zostanie oznaczony jako przestarzały, a w przyszłości planowane jest jego całkowite usunięcie.
Zainstaluj teraz i czytaj tylko dobre teksty!
Preview
455: Primitive Types in Patterns, instanceof, and switch (Preview)
JEP 455 wprowadza pattern matching dla typów prymitywnych w instanceof
i switch
, umożliwiając użycie tych typów w kontekstach zagnieżdżonych i na tak zwanym top-level.
Jaki jest problem?: Obecne ograniczenia dotyczące typów prymitywnych w instanceof
i switch
powodują trudności w pisaniu w oparciu o nie jednolitego i ekspresyjnego kodu. Na przykład, switch
nie obsługuje wzorców typu prymitywnego, a instanceof
nie wspiera bezpiecznego rzutowania na typy prymitywne, co prowadzi do niespójnego, potencjalnie skomplikowanego kodu.
Solution: JEP 455 umożliwia użycie pattern matchingu typów prymitywnych w instanceof
i switch
, co pozwala na bezpieczne i ekspresyjne rzutowanie. To eliminuje potrzebę ręcznych, potencjalnie niebezpiecznych rzutowań oraz umożliwia bardziej zwięzły kod.
Przykład kodu:
switch (x.getStatus()) {
case 0 -> "okay";
case 1 -> "warning";
case 2 -> "error";
case int i -> "unknown status: " + i;
}
if (i instanceof byte b) {
... // użyj b
}
466: Class-File API (Second Preview)
JEP 466 proponuje standardowe API do parsowania, generowania i transformowania plików klas Java. Jest to druga wersja preview API, która została udoskonalona na podstawie doświadczeń i opinii z pierwszej wersji preview wprowadzonej w JEP 457.
Jaki jest problem?: Obecnie istnieje wiele bibliotek do obsługi plików klas Java, jednak każda z nich ma swoje wady i zalety. Problemem jest szybka ewolucja formatu plików klas, co powoduje, że biblioteki te nie zawsze są aktualne w stosunku do najnowszych wersji JDK. To prowadzi do problemów z kompatybilnością i utrudnia wprowadzanie nowych funkcji w JDK, które to samo używam min. biblioteki ASM.
Solution: JEP 466 wprowadza standardowe API do obsługi plików klas, które będzie ewoluować razem z formatem plików klas. API to zapewnia:
- Niezmienność obiektów reprezentujących elementy plików klas.
- Strukturę drzewa, która odzwierciedla hierarchię pliku klasy.
- Nawigację sterowaną przez użytkownika, co pozwala na efektywne parsowanie tylko niezbędnych części pliku klasy.
- Lenistwo w parsowaniu, co zwiększa efektywność.
- Zintegrowane widoki strumieniowe i materializowane.
- Transformacje jako operacje płaskiego mapowania na strumieniach elementów.
- Ukrywanie szczegółów implementacyjnych, takich jak pula stałych czy mapy stosu.
API Class-File ma jak widać spory zakres i musi generować klasy zgodnie ze specyfikacją Java Virtual Machine, dlatego wymaga znaczących testów jakości i zgodności. W miarę zastępowania użycia ASM w JDK przy użyciu API Class-File, wyniki będą porównywane, aby wykryć regresje, a także przeprowadzać obszerne testy wydajności.
Jakie zmiany pojawiły się od ostatniej wersji preview?
- Uproszczono klasę
CodeBuilder
, usuwając metody średniego poziomu, które dublowały metody niskiego poziomu lub były rzadko używane. - Udoskonalono klasę
ClassSignature
, aby dokładniej modelowała sygnatury generyczne superklas i superinterfejsów.
**Alternatywy: ** Rozważano alternatywę polegającą na włączeniu ASM do JDK i przejęciu odpowiedzialności za jego dalsze utrzymanie, ale uznano, że nie jest to właściwe rozwiązanie. ASM to stara, legacy baza kodu i trudna do ewolucji, a priorytety projektowe, które kierowały jego architekturą, mogą nie pasować do dzisiejszych realiów.
Przykład kodu
Parsowanie plików klas za pomocą wzorców:
CodeModel code = ...
Set<ClassDesc> deps = new HashSet<>();
for (CodeElement e : code) {
switch (e) {
case FieldInstruction f -> deps.add(f.owner());
case InvokeInstruction i -> deps.add(i.owner());
// ... and so on for instanceof, cast, etc ...
}
}
Generowanie plików klas za pomocą builderów:
ClassBuilder classBuilder = ...;
classBuilder.withMethod("fooBar", MethodTypeDesc.of(CD_void, CD_boolean, CD_int), flags,
methodBuilder -> methodBuilder.withCode(codeBuilder -> {
Label label1 = codeBuilder.newLabel();
Label label2 = codeBuilder.newLabel();
codeBuilder.iload(1)
.ifeq(label1)
.aload(0)
.iload(2)
.invokevirtual(ClassDesc.of("Foo"), "foo", MethodTypeDesc.of(CD_void, CD_int))
.goto_(label2)
.labelBinding(label1)
.aload(0)
.iload(2)
.invokevirtual(ClassDesc.of("Foo"), "bar", MethodTypeDesc.of(CD_void, CD_int))
.labelBinding(label2)
.return_();
});
Transformacja plików klas:
ClassFile cf = ClassFile.of();
ClassModel classModel = cf.parse(bytes);
byte[] newBytes = cf.transform(classModel, (classBuilder, ce) -> {
if (ce instanceof MethodModel mm) {
classBuilder.transformMethod(mm, (methodBuilder, me)-> {
if (me instanceof CodeModel cm) {
methodBuilder.transformCode(cm, (codeBuilder, e) -> {
switch (e) {
case InvokeInstruction i
when i.owner().asInternalName().equals("Foo") ->
codeBuilder.invoke(i.opcode(), ClassDesc.of("Bar"),
i.name().stringValue(),
i.typeSymbol(), i.isInterface());
default -> codeBuilder.with(e);
}
});
} else {
methodBuilder.with(me);
}
});
} else {
classBuilder.with(ce);
}
});
473: Stream Gatherers (Second Preview)
JEP 473 proponuje ulepszenie API Stream, aby obsługiwało niestandardowe operacje pośrednie, umożliwiając bardziej elastyczne przetwarzanie strumieni.
Jaki jest problem?: Obecne API Stream oferuje stały zestaw operacji pośrednich, które nie zawsze umożliwiają realizację bardziej złożonych zadań. Brak możliwości definiowania niestandardowych operacji pośrednich ogranicza elastyczność i ekspresyjność kodu, zwłaszcza w przypadku zadań, które wymagają specyficznej logiki transformacji danych.
Solution: JEP 473 wprowadza nową operację pośrednią Stream::gather(Gatherer)
, która umożliwia przetwarzanie elementów strumienia przy użyciu zdefiniowanych przez użytkownika tzw. gathererów. Gatherer to instancja interfejsu java.util.stream.Gatherer
, która reprezentuje transformację elementów strumienia i umożliwia manipulację strumieniami o nieskończonej wielkości.
Jakie zmiany pojawiły się od ostatniej wersji preview?: Nie wprowadzono żadnych zmian w porównaniu do poprzedniej wersji preview (JEP 461). Celem jest zebranie dodatkowych opinii i doświadczeń z użytkowania tego API.
Przykład kodu:
Grupowanie elementów w stałe okna:
var result = Stream.iterate(0, i -> i + 1)
.gather(Gatherers.windowFixed(3))
.limit(2)
.toList();
// result ==> [[0, 1, 2], [3, 4, 5]]
Definiowanie niestandardowego gatherera:
record WindowFixed<TR>(int windowSize)
implements Gatherer<TR, ArrayList<TR>, List<TR>> {
public WindowFixed {
if (windowSize < 1) {
throw new IllegalArgumentException("window size must be positive");
}
}
@Override
public Supplier<ArrayList<TR>> initializer() {
return () -> new ArrayList<>(windowSize);
}
@Override
public Integrator<ArrayList<TR>, TR, List<TR>> integrator() {
return Gatherer.Integrator.ofGreedy((window, element, downstream) -> {
window.add(element);
if (window.size() < windowSize) {
return true;
}
var result = new ArrayList<TR>(window);
window.clear();
return downstream.push(result);
});
}
@Override
public BiConsumer<ArrayList<TR>, Downstream<? super List<TR>>> finisher() {
return (window, downstream) -> {
if (!downstream.isRejecting() && !window.isEmpty()) {
downstream.push(new ArrayList<TR>(window));
window.clear();
}
};
}
}
476: Module Import Declarations (Preview)
JEP 476 wprowadza możliwość importowania wszystkich pakietów eksportowanych przez moduł za pomocą nowej deklaracji import module
. Ułatwia to ponowne wykorzystanie bibliotek modularnych bez konieczności modularizacji własnego kodu.
**Jaki jest problem?: ** Obecnie programiści w wypadku używania modułów muszą ręcznie importować wiele pakietów, co może być czasochłonne i skomplikowane, zwłaszcza dla początkujących użytkowników. Brak możliwości prostego importowania całych modułów prowadzi do redundancji i złożoności kodu.
**Jak proposal rozwiązuje ten problem?: ** JEP 476 wprowadza nową deklarację import module
, która pozwala importować wszystkie publiczne klasy i interfejsy z pakietów eksportowanych przez dany moduł oraz moduły, które są przez niego transitively wymagane. Dzięki temu programiści mogą łatwiej i szybciej uzyskać dostęp do potrzebnych zasobów, co upraszcza kod i zmniejsza liczbę deklaracji importu.
Nie ma jednak róży bez kolców – użycie wielu deklaracji import module
może prowadzić do ryzyka konfliktów nazw, które zostaną wykryte dopiero podczas kompilacji. Rozwiązanie tych konfliktów może wymagać dodania pojedynczych deklaracji importu typu, co może być uciążliwe i prowadzić do trudności w utrzymaniu kodu.
Przykład kodu:
Importowanie całego modułu:
import module java.base;
String[] fruits = new String[] { "apple", "berry", "citrus" };
Map<String, String> m =
Stream.of(fruits)
.collect(Collectors.toMap(
s -> s.toUpperCase().substring(0,1),
Function.identity()));
Rozwiązywanie konfliktów nazw:
import module java.base;
import module java.sql;
import java.sql.Date;
Date d = ... // Ok! Date jest rozpoznane jako java.sql.Date
477: Implicitly Declared Classes and Instance Main Methods (Third Preview)
JEP 477 ma na celu uproszczenie pisania pierwszych programów w języku Java, umożliwiając programistom tworzenie programów bez potrzeby zrozumienia zaawansowanych konstrukcji językowych przeznaczonych do dużych programów. Funkcja ta wprowadza możliwość deklarowania klas implicit i metod main instancji, co pozwala na bardziej zwięzłe pisanie małych programów.
Jaki jest problem?: Obecnie początkujący programiści muszą zrozumieć wiele złożonych konstrukcji językowych, takich jak klasy, pakiety, moduły, modyfikatory dostępu i statyczne, zanim będą mogli napisać prosty program. To może zniechęcać nowych użytkowników i utrudniać naukę.
Solution: JEP 477 upraszcza tworzenie programów wprowadzając:
- Instancyjne metody main: Metody main nie muszą być statyczne, publiczne ani przyjmować parametru
String[]
. - Implicitne klasy: Pozwala to na pisanie metod i pól na najwyższym poziomie, które są automatycznie zawierane w ukrytej klasie.
- Automatyczny import metod I/O: Metody do prostego wejścia/wyjścia tekstowego są automatycznie importowane.
- Automatyczny import modułu java.base: Wszystkie publiczne klasy i interfejsy pakietów eksportowanych przez moduł java.base są automatycznie importowane.
Przykład kodu:
Prosty program „Hello, World!” w implicitnej klasie:
void main() {
println("Hello, World!");
}
Program interaktywny:
void main() {
String name = readln("Please enter your name: ");
print("Pleased to meet you, ");
println(name);
}
480: Structured Concurrency (Third Preview)
JEP 480 wprowadza API do strukturalnej współbieżności, upraszczając programowanie współbieżne poprzez traktowanie grup powiązanych zadań jako jednej „jednostki pracy”. Strukturalna współbieżność poprawia obsługę błędów i ułatwia anulowanie zadań, dodatkowo zapewniając obserwowalność kodu współbieżnego.
Jaki jest problem?: Obecne podejście do współbieżności, oparte na ExecutorService i Future, pozwala na nieograniczone żadnymi logicznymi zakresami wzorce współbieżności, co prowadzi do problemów z zarządzaniem życiem wątków, obsługą błędów i propagacją anulowania. To sprawia, że kod jest trudniejszy do zrozumienia, debugowania i utrzymania.
Jak proposal rozwiązuje ten problem?: JEP 480 wprowadza API do strukturalnej współbieżności, które zapewnia hierarchiczne relacje między zadaniami i ich podzadaniami, podobnie jak stos wywołań w standardowym kodzie. StructuredTaskScope
, główna klasa API, umożliwia programistom grupowanie powiązanych zadań, zarządzanie nimi jako jedną jednostką oraz automatyczną propagację anulowania i obsługę błędów. Zwiększa też observability – narzędzia tego typu mogą bowiem wyświetlać hierarchię zadań, co ułatwia diagnozowanie problemów
Przykład kodu:
Użycie StructuredTaskScope w metodzie handle:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier<String> user = scope.fork(() -> findUser());
Supplier<Integer> order = scope.fork(() -> fetchOrder());
scope.join() // Dołącz do obu podzadań
.throwIfFailed(); // Propaguj błędy
// Tutaj oba podzadania zakończyły się sukcesem, więc komponuj ich wyniki
return new Response(user.get(), order.get());
}
}
481: Scoped Values (Third Preview)
JEP 481 wprowadza wartości zakresowe (scoped values), które umożliwiają metodzie dzielenie się niemutowalnymi danymi zarówno z wywołaniami wewnątrz wątku, jak i z wątkami potomnymi.
**Solution: ** Zmienne lokalne wątku (ThreadLocal) są trudne do zarządzania i mają problemy z mutowalnością, nieograniczoną żywotnością oraz kosztownym dziedziczeniem. Te problemy stają się jeszcze bardziej widoczne przy korzystaniu z dużej liczby wątków wirtualnych.
Jak proposal rozwiązuje ten problem?: JEP 481 wprowadza wartości zakresowe jako bezpieczny i wydajny sposób na dzielenie danych między metodami w tym samym wątku oraz z wątkami potomnymi. Są one niemutowalne, a ich ograniczona żywotność sprawia, że są dostępne tylko przez określony czas podczas wykonywania wątku, co upraszcza zarządzanie i poprawia wydajność. Mogą być również dziedziczone przez wątki potomne tworzone przez StructuredTaskScope
.
Scoped Values cechuje łatwość użycia, oferując prosty sposób na przekazywanie danych między metodami, oraz przejrzystość, gdyż żywotność udostępnianych danych jest wyraźnie widoczna w strukturze kodu. Są one łatwiejsze do zrozumienia niż zmienne lokalne wątku i mają niższe koszty pamięci i czasu, szczególnie gdy są używane razem z wątkami wirtualnymi (JEP 444) i zorganizowaną współbieżnością (JEP 480).
Przykład użycia wartości zakresowych w frameworku webowym:
class Framework {
private final static ScopedValue<FrameworkContext> CONTEXT
= ScopedValue.newInstance();
void serve(Request request, Response response) {
var context = createContext(request);
ScopedValue.runWhere(CONTEXT, context,
() -> Application.handle(request, response));
}
public PersistedObject readKey(String key) {
var context = CONTEXT.get();
var db = getDBConnection(context);
db.readKey(key);
}
}
W tym przykładzie CONTEXT
jest wartością zakresową, która jest ustawiana w metodzie serve
i dostępna w metodzie readKey
.
482: Flexible Constructor Bodies (Second Preview)
JEP 482 wprowadza możliwość umieszczania instrukcji w konstruktorach przed wywołaniem innego konstruktora (super(..)
lub this(..)
) w języku Java. Instrukcje te nie mogą odwoływać się do tworzonej instancji, ale mogą inicjalizować jej pola.
Jaki jest problem?: W języku Java konstruktor musi zaczynać się od wywołania innego konstruktora (super(..)
lub this(..)
). To ograniczenie uniemożliwia umieszczanie kodu inicjalizacyjnego przed wywołaniem konstruktora nadklasy, co może prowadzić do problemów z nadpisywaniem metod i inicjalizacją pól.
Solution: JEP 482 wprowadza zmiany do gramatyki konstruktorów, pozwalając na umieszczanie instrukcji przed wywołaniem konstruktora. Kod ten może inicjalizować pola, ale nie może odwoływać się do tworzonej instancji. Dzięki temu klasa jest bardziej niezawodna, gdy metody są nadpisywane.
Przykład weryfikacji argumentów konstruktora przed wywołaniem konstruktora nadklasy:
public class PositiveBigInteger extends BigInteger {
public PositiveBigInteger(long value) {
if (value <= 0) throw new IllegalArgumentException(..);
super(value);
}}
W tym przykładzie argument value
jest weryfikowany przed wywołaniem konstruktora nadklasy BigInteger
.
Zainstaluj teraz i czytaj tylko dobre teksty!
Incubation
469: Vector API (Eighth Incubator)
JEP 469 rozszerza API, które umożliwia wyrażanie obliczeń wektorowych, kompilowanych do natywnych instrukcji wektorowych na obsługiwanych architekturach CPU, oferując wydajność przewyższającą równoważne obliczenia skalarne.
What issue do they address?: Koncepcja obliczeń wektorowych, która umożliwia wykonywanie operacji na wielu danych jednocześnie, była trudna do wyrażenia w Javie. W związku z tym środowisko polegało ona na algorytmie auto-wektoryzacji HotSpot, co z kolei ograniczało jej praktyczną użyteczność i wydajność.
Solution: Vector API w języku Java umożliwia tworzenie skomplikowanych algorytmów wektorowych z lepszą przewidywalnością i niezawodnością. Wykorzystuje istniejący auto-wektoryzator HotSpot, oferując użytkownikowi bardziej przewidywalny model.
Changes since the last incubator: API zostało ponownie wprowadzone do inkubacji w JDK 22, z drobnymi ulepszeniami z JDK 21, takimi jak poprawki błędów i zwiększenie wydajności. Oczekuje się, że przed wprowadzeniem wersji Preview zostanie zakończony Projekt Valhalla.