Java 22 and GraalVM for JDK 22 Released! - JVM Weekly vol. 76
And there we have it, the publication of the new JDK and GraalVM is behind us. Without further ado, let's unpack this present.
1. JDK 22 Released
Let's start with the Release Party to "feel the room."
And now, I invite you to the list of new JEPs.
And yes, I know that it's essentially a Reprise (shout out to musical fans!) of the text that appeared during the Rampdown phase... but you know, not much has changed in JDK 22 in the meantime 😊.
Besides, if you scroll down, you'll also find updates from GraalVM for JDK 22.
Stable
JEP 423: Region Pinning for G1
JEP 423 is designed to decrease latency in G1 through the introduction of "region pinning". This ensures that the Garbage Collection processes don't have to be turned off in the critical regions of the Java Native Interface.
The critical region in JNI is the particular code segment where a thread directly interacts with Java objects. Within this region, a thread can directly alter the data of the object, which is vital when interfacing with 'unmanaged' programming languages like C and C++. The original JEP provides a very understandable explanation of this concept.
What issue do they address?: When a thread is in a JNI critical region, the JVM has been required to prevent the movement of objects within it during the garbage collection process. G1 turns off the GC in each critical region, which greatly impacts its latency for JNI code.
Solution: JEP enhances G1 by providing the feature to pin any regions. This process includes maintaining a list of critical objects in each region: the count increases when a critical object is acquired and decreases when it is let go. Regions that have a count other than zero are deemed 'pinned' and are not cleared out during GC.
PS: If you're curious about other details regarding changes in Garbage Collectors, as with every JDK release, there is an article about changes (even minor ones) in this area by Thomas Schatzl. As always, I recommend it.
JEP 454: Foreign Function & Memory API
JEP 454 aims to substitute JNI with a more straightforward, comprehensible API in pure Java. It also seeks to enhance performance, offer extensive platform compatibility, and ensure the consistency and integrity of operations on native code and data.
What issue do they address? Java developers frequently have to interact with resources that are not directly managed by the JVM, particularly code that's compiled in C languages and data stored in native memory. However, the existing APIs don't offer a straightforward and safe method to accomplish this. This constraint hampers smooth interaction with other platforms and the utilization of native libraries and data, an area where languages like Python excel.
Solution: JEP 454 presents the Functions External and Memory (FFM API) API as a solution to these issues. This API enables Java programs to call external functions (code not within control of the JVM) and securely access external memory (memory not managed by the JVM), serving as a replacement for the unstable, unsafe, and generally unpopular JNI. The goal of the API is to allow Java programs to call native libraries and process native data in a more reliable (with the introduction of typing) and flexible manner.
JEP 456: Unnamed Variables & Patterns
JEP 456 is designed to enhance code readability and maintainability by allowing developers to "label" variables that need to be created in certain contexts, even though they may not be practically used. Additionally, it seeks to improve the handling of patterns in switch and case statements.
Original issue: It's common for developers to declare variables they don't plan on using, either for style purposes or due to the demands of the programming language. The unspoken belief that these variables won't be used can cause mistakes and uncertainty in the code. They can also misleading to a variety of linters and other code assistance tools, which are becoming more and more prevalent.
Solution: JEP 456 brings in 'unnamed' variables and patterns. These can be utilized when there's a need for variable declarations or nested patterns, but they are never used. Both kinds are represented by the underscore character, _. This provides a clear sign that a variable or lambda parameter is not used, thereby enhancing the readability and maintainability of the code, and offering improved tool support.
In practice:
Unnamed Variable
for (Order _ : orders) {
total++;
}
Unnamed Pattern
switch (obj) {
case Integer _:
System.out.println("This is an Integer, but its value is not needed.");
break;
// Other cases...
}
JEP 458: Launch Multi-File Source-Code Programs
JEP 458 simplifies the process of gradually transitioning from the development of basic programs to more complex ones. It allows you to effortlessly build and execute applications made up of multiple source files, eliminating the need for manual compilation of each one.
PS: The entire concept aligns well with 463: Implicitly Declared Classes and Instance Main Methods (Second Preview), which can be found slightly further down.
What issue do they address?: In the past, executing programs written in Java necessitated the compilation of the source code into .class
files prior to running them. JEP 330 enabled the direct execution of individual source files, however, the entire program had to be contained within a single .java
file, and as you know, in the JVM world...
Solution: JEP 458 enhances the ability to run programs using the java
command, enabling programs that are delivered as several Java source files to be executed. Now, you can run a program made up of multiple .java
files without the need for compilation, simplifying the script writing process.
In practice: If we have two files, Main.java and Helper.java, you can run Main.java using java Main.java
, and the program will automatically find and compile Helper.java (in some cases it may be necessary to define a directory structure or classpath).
Preview
JEP 447: Statements before super(...) (Preview)
The concept of "pre-constructor context" is introduced by this JEP. This includes both the arguments of an explicit constructor call by super
and all instructions that occur prior to it. The rules for this context are similar to those of normal instance methods, with the exception that the code cannot refer to the instance being created.
What issue do they address?: In the past, constructors required the initial statement to be a call to the parent class's constructor super(...)
or another constructor of the identical class this(...)
. This limitation posed challenges in incorporating the necessary logic to generate arguments for super
, which had to be situated in static helper methods, indirect helper constructors, or constructor arguments.
Solution: JEP 447 brings a modification in the syntax of constructors, permitting instructions that don't reference the instance being constructed to be positioned prior to an explicit constructor call. The objective is to offer more flexibility in defining the behaviour of constructors, while ensuring that constructors function 'top-down' during the creation of a class instance.
In practice:
class SubClass extends SuperClass {
SubClass(int param) {
// Logic before constructor
int calculatedValue = someStaticMethod(param);
super(calculatedValue); // Wywołanie konstruktora klasy nadrzędnej
// Additional logic after executing super(...)
}
private static int someStaticMethod(int param) {
return param * 2;
}}
JEP 457: Class-File API (Preview)
The aim of JEP 457 is to offer a standard API for parsing, generating, and altering Java class files.
What issue do they address?: In the Java ecosystem, class files are vital for parsing, generating, and transforming. However, current class file processing libraries like ASM, BCEL, or Javassist, often struggle to keep pace with the fast changes in the class file format introduced in the JDK. This results in version incompatibility issues and errors that application developers can see.
Solution: JEP 457 suggests a standard API for parsing, generating, and altering Java class files in accordance with the class
file format outlined by the JVM specification. This will enable JDK components to transition solely to it, thereby removing the necessity for an internal copy of the ASM library in the JDK.
Why is this happening right now? The developers suggest that the progression of the JVM has significantly sped up, with an increasing number of modifications occurring directly at the generated code level. They believe that with the introduction of the new API, major projects like Valhalla will cause less disruption to the overall ecosystem.
Example of parsing class files with patterns:
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());
// ... i tak dalej dla instanceof, cast, itd.
}
}
JEP 459: String Templates (Second Preview)
JEP 459 presents String templates as a fresh expression category in Java. Text templates aid in the generation of lengthy strings by merging hardcoded text with integrated expressions and template processors to achieve pre-filled result.
What issue do they address?: The main issue is that Java programmers often construct strings by combining fixed text with expressions. Current string concatenation methods in Java, like using the + operator, are inconvenient and may result in hard-to-maintain code. On the other hand String.join
is not powerful enough for complex uses.
Solution: String Templates represent a new language construct in Java, facilitating string interpolation in a more sophisticated manner compared to previous methods. They offer a secure and efficient approach to string composition. For instance, the STR template processor carries out string interpolation by substituting each embedded expression in the template with its appropriately formatted value.
Example code:
String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan"); // true
JEP 461: Stream Gatherers (Preview)
The Stream Gatherer interface signifies the transformation of Stream elements. Gatherers cam transform elements in several ways: one-to-one, one-to-many, many-to-one, many-to-many. They also have the capability to keep a record of previously encountered elements to influence the transformation of subsequent elements. Moreover, they can halt processing to transform infinite streams into finite streams, thereby facilitating their parallelized execution.
What issue do they address?: Despite the Stream API from JDK 1.8 offering a comprehensive range of intermediate and final operations like mapping, filtering, reducing, and sorting, it lacked the capability to expand this set. This implied that certain more complex tasks couldn't be defined as streams due to the absence of the required intermediate operation.
The contributions of JEP: JEP 461 presents the intermediate operation Stream::gather(Gatherer)
to the Stream API, which enables stream elements to undergo processing through a user-specified group of operations known as 'gatherers'. This enhances the functionality of the Stream API, now capable of mapping nearly any intermediate operation.
Code example: Suppose the task is to take a stream of strings and make it unique, but with uniqueness based on the length of the string rather than its content:
var result = Stream.of("foo", "bar", "baz", "quux")
.gather(new DistinctByLengthGatherer())
.toList();
public class DistinctByLengthGatherer implements Gatherer<String, String, Set<Integer>, List<String>> {
@Override
public Supplier<Set<Integer>> supplier() {
return HashSet::new;
}
@Override
public BiConsumer<Set<Integer>, String> accumulator() {
return (seen, string) -> {
if (seen.add(string.length())) {
seen.add(string);
}
};
}
@Override
public BinaryOperator<Set<Integer>> combiner() {
return (seen1, seen2) -> {
seen1.addAll(seen2);
return seen1;
};
}
@Override
public Function<Set<Integer>, List<String>> finisher() {
return ArrayList::new;
}
}
In this instance, the hypothetical operation .gather(new DistinctByLengthGatherer())
serves as an example of a Gatherer application.
JEP 462: Structured Concurrency (Second Preview)
JEP 462 presents an API for Structured Concurrency, which simplifies the management of groups of related tasks running in different threads as a single work unit. This method enhances error handling and cancellation, thereby improving reliability and observability.
The contributions of JEP: That concurrent programming can be complex, particularly when it comes to managing several tasks that are executing simultaneously. Current methods frequently face the potential hazards of thread leaks, delays in cancellation, and difficulties in observing concurrent code.
Solution: The primary class in the structured concurrency API is StructuredTaskScope
from the java.util.concurrent
package. This class enables you to organize a task into a set of concurrent subtasks and manage them as units within a specific structure. These subtasks run in their own threads and are then controlled, combined, and cancelled, including cascaded cancellation.
Example of use:
Response handle() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Supplier user = scope.fork(() -> findUser());
Supplier order = scope.fork(() -> fetchOrder());
scope.join()
.throwIfFailed();
return new Response(user.get(), order.get());
}
}
JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview)
Enhances the capability to write main
methods straight at the level of the source file, bypassing the need to place them in a class. This simplifies the code structure, particularly for novices.
What issue do they address?: Java, despite its effectiveness in building large, intricate applications, can pose difficulties for beginners, particularly when contrasted with Python (once again). The initial stages of learning Java frequently require understandment of complex concepts that are not needed for writing basic programs.
Solution: JEP 463 brings in implicitly declared classes and main methods for instances in Java, simplifying the process of writing basic programs. It offers a more user-friendly and less complicated method for creating initial programs, all the while ensuring complete compatibility and the potential for expansion to more sophisticated language features as the programmer's skills improve.
Practical Example:
void main() {
System.out.println("Hello, World!");
}
This example illustrates how, with a simplified structure, it's possible to write a program that displays "Hello, World!" without the need for explicit declaration of a class and a static main
method.
JEP 464: Scoped Values (Second Preview)
JEP 464 introduces Scoped Values, which enable the secure and efficient transfer of "confined" data within a specific scope, ensuring immutability within a single thread and also between threads. They are the preferred alternative to Thread.Local
 especially when using a large number of virtual threads.
Primary problem: In Java, methods often require data to be passed as parameters, which can be impractical when each indirect call needs different data. Thus, a mutable context is often created, which is a kind of 'data bag', passed via parameters or held in thread memory."
Solution: Scoped values enable data sharing among methods in a comprehensive program, eliminating the need for method parameters. These are of the ScopedValue
type and are typically declared as final static private
to hinder direct access from other classes.
Usage example:
ScopedValue.where(NAME, <value>)
.run(() -> { ... NAME.get() ... call methods ... });
In this example, the term ScopedValue.where
is used to define a scoped value and the object it should be bound to. When run
is called, it binds the scoped value, creating a copy that is specific to the current thread, and then executes the lambda expression that was passed. While the run
call is being processed, the lambda expression, or any method that is called directly or indirectly from that expression, has the ability to read the scoped value using the get()
method.
Incubation
JEP 460: Vector API (Seventh Incubator)
JEP 460 expands the API that allows for the expression of vector computations, which compile into native vector instructions on supported CPU architectures, offering performance that surpasses equivalent scalar computations.
What issue do they address?:Â The concept of vector computing, which enables operations to be performed on multiple data simultaneously, was challenging to articulate in Java. It relied on the HotSpot auto-vectorization algorithm, which in turn limited its practical usability and performance.
Solution: The Vector API in Java enables the development of intricate vector algorithms with enhanced predictability and reliability. It utilizes the existing HotSpot auto-vectorizer, providing a more predictable model for the user.
Changes since the last incubator: The API was reintroduced for incubation in JDK 22, featuring minor enhancements from JDK 21, such as bug fixes and performance boosts. The completion of Project Valhalla is still anticipated before Preview.
2. GraalVM for JDK 22 (also) Released!
As has been the case since the unification of the lifecycle of both projects, alongside OpenJDK 22, GraalVM for JDK 22 was released. Also there, you can watch the launch party with a description of the features in the video below:
and in a concise form below, I will try to briefly describe the most interesting new features (a bit more concisely than in the case of OpenJDK 22, because the GraalVM release announcement from the blog by Alina Yurenko is very accessible by itself).
Probably the most significant new feature in this version of GraalVM is the introduction of a new default class initialization strategy (introduced as an experimental feature in GraalVM for JDK 21), as now it has been thoroughly tested and validated for broader use. It allows all classes to be utilized and initialized during the image generation process, regardless of their previous class initialization configuration settings. This mode simplifies the distinction between classes intended for build-time versus run-time initialization, facilitating a more flexible and straightforward approach to class initialization within image builds. For classes designated for build-time initialization, their static fields are preserved into the runtime, maintaining their state from the image generator. Conversely, classes marked for run-time initialization appear as uninitialized at runtime, even if they were previously initialized during the build, effectively mirroring the current approach for "re-run initialization at run time" but without the complexity and potential for unintended side effects.
The release also introduces the Oracle GraalVM Buildpack, making it easier to integrate GraalVM with projects, especially those based on Spring Boot. Paketo Buildpacks are a set of open-source buildpacks designed for building and deploying applications in containerized environments. They follow the Cloud Native Buildpacks (CNB) specification, which provides a standardized approach to building containers by automating the process of fetching dependencies, configuring frameworks, and packaging applications for deployment. You can read more in the announcement post from December.
GraalVM also introduced significant improvements in the profiling of Native Image through support for so-called Flame Graphs, familiar to all users of, for example, Async-Profiler. This feature offers visual insight into the performance of individual methods.
Staying on the topic of broadly understood application monitoring, GraalVM for JDK 22 brings new capabilities within Profile-Guided Optimization, including an experimental option to evaluate the quality of generated profiles, which provides metrics describing the "applicability" and actual impact of profiles on performance, ultimately aiming to enhance the efficiency of building Native Image.
However, that's not all, as the update, keeping pace with JDK 22 itself, includes support for most of the new features of the new release, including experimental support for Project Panama and its Foreign Function & Memory API, intended to improve Java's interaction with native code. Also noteworthy are the improvements in developer experiences for Native Image, such as automatic reflection registration for some Java classes and support for the NATIVE_IMAGE_OPTIONS environment variable.
And finally, a bonus, so it doesn’t look like I’m just copying the release notes and not adding anything of my own 😊. If you’re curious about the whole "business" background of the project, Thomas Wuerthinger during last year's Strange Loop (IMHO the most interesting programming conference in the world, I must go sometime) shared a ton of interesting details in his talk Turning GraalVM from Research to Product. I highly recommend it!
1. Any time you decide to NOT include cartoons depicting brain surgery, in any form, I'm more than happy to support you. In fact, I wouldn't mind at all if you never, ever showed one of those again. Seriously, that would be fine with me.
2. According to Google translate, "Wywołanie konstruktora klasy nadrzędnej" means "Calling the constructor of the parent class," which totally works. Now that I know what it means, it even sort of looks like that. If I could pronounce it correctly, I would use it in regular conversation.
3. I always liked unnamed variables from Groovy and Kotlin. I'm glad Java has them at last.
4. Code before this() or super() is something I didn't know that I needed, but now I think I do.
5. I thought they were considering getting rid of the "STR" part of string templates. Was that just speculation? Of course, I could just look in the JEP, but that would delay making snarky comments here.
6. Coroutines in Kotlin didn't really take off until they added structured concurrency, so I'm glad that's making progress.
7. A Lisp (or possibly a Clojure) joke for you: Did you hear that foreign hackers stole the last 5 Gigs of Lisp (Clojure) source code governing the nuclear launch codes? Don't worry, though. It was all close parentheses.
8. Let's be honest. The Vector API will be released just before the heat death of the universe, but even then it will still be in beta.
9. Great job, as always :)