Topic: JDK Release Level: Basic
Introducing JDK 19
Java 19 is the 10th release since the transition to the 6-month release cadence of new JDK features, comprising of 7 JDK Enhancement Proposals.
With the ever-releasing new JDK features catching up on the latest version could be a daunting task. However it is a gift in disguise, with the shorter releases,
- Applications can effortlessly migrate to the successor versions by incorporating fewer minimal additions/deprecations in the features rather than having big bang migration housing innumerable changes.
- The steep learning curve and adaptation curve on the features introduced can be significantly lessened as the release cycles are periodic and incremental.
- Nurtures prompt experimentation and feedback for improvement and development of the released features (incubate and preview).
Constitutes of JDK 19
- (JEP 428) Structured Concurrency (Incubator)
- Improve the maintainability, reliability, and observability of your multithreaded code.
- (JEP 405) Record Patterns (Preview)
- Discover how to extend pattern matching to express more sophisticated, composable data queries.
- (JEP 424) Foreign Function and Memory API (Preview)
- Enable interoperability with code and data outside the Java runtime.
- (JEP 425) Virtual Threads (Preview)
- Reduce the effort of writing, maintaining, and observing high-throughput, concurrent applications.
- (JEP 427) Pattern Matching for Switch (Third Preview)
- Expand the expressiveness and applicability of switch expressions and statements by allowing patterns to appear in case labels.
- (JEP 426) Vector API (Fourth Incubator)
- Express vector computations that reliably compile at runtime to optimal vector instructions on supported CPU architectures, thus achieving performance superior to equivalent scalar computations.
- (JEP 422) Linux/RISC-V Support
- With the increasing availability of RISC-V hardware, access the JDK to Linux/RISC-V to expand your hardware reach.
- Project Amber - Continuously improve developer productivity through evolutions of the Java language
- 405: Record Patterns (Preview)
- 427: Pattern Matching for Switch (Third Preview)
- Project Leyden - Improve start-up time to achieve peak performance
- Project Loom - Massively scale lightweight threads, making concurrency simple
- 425: Virtual Threads (Preview)
- 428: Structured Concurrency (Incubator)
- Project Panama - High performance with easier creation of I/O intensive apps through Java-native platform changes (library)
- 424: Foreign Function and Memory API (Preview)
- 426: Vector API (Fourth Incubator)
- 422: Linux/RISC-V Port
- Project Valhalla - Higher memory density, better performance of ML and big data apps through the introduction of value types
- Project ZGC - Create a scalable low latency garbage collector capable of handling large heaps
On to the Lancing Details,
405: Record Patterns (Preview)
Introduced from JDK 16 onwards, the following code snippet compiles
if (o instanceof String) {
String s = (String)o;
... use s ...
}
//New code
if (o instanceof String s) {
... use s ...
}
In the snippet heed from the //New code, that when the parameter passed 'o' is found to be an instance belonging to String then the value of 'o' will be assigned to the pattern variable 's', so as to avoid the cast that if to follow when consuming (ref. above // Old code)
From the JDK 17 and 18 ahead, the type pattern ideology has been applied to the switch case labels.
Using Pattern matching and record classes,
static void printSum(Object o) {
if (o instanceof Point p) { /* pattern variable p is used to extract the values x() and y() */
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
//New code
record Point(int x, int y) {}
void printSum(Object o) {
if (o instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
//Advanced example,
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
}
The ColoredPoint is in turn a record of its own containing Point record and Enum, here the records are nested as such the notion of extracting and initializing the record variables applies as below defined code,
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point p, Color c),
ColoredPoint lr)) {
System.out.println(c);
}
}
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
var lr)) {
System.out.println("Upper-left corner: " + x);
}
}
The record Point performs type inference and var is used which gets resolved on runtime.
427: Pattern Matching for Switch (Third Preview)
Introduced in JDK 17,
String formatted = "unknown";
if (o instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (o instanceof Long l) {
formatted = String.format("long %d", l);
} else if (o instanceof Double d) {
formatted = String.format("double %f", d);
} else if (o instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
Constructing the case labels of pattern type transforms the if-else construct as,
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};
}
Integrate the null test into the switch by allowing a new null case label:
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
The Guarded Pattern by evaluating a condition and executing only if the same holds true,
switch (o) {
case String s when s.length() == 1 -> ...
case String s -> ...
...
}
}
The first clause matches if the Object 'o' is both a String and of length 1. The second case matches if the Object 'o' is a String of any length.
The type of the selector expression be either an integral primitive type (excluding long) or any reference type, an enum type, a record type, and an array type, along with a null case label and a default:
enum Color { RED, GREEN, BLUE; }
static void typeTester(Object o) {
switch (o) {
case null -> System.out.println("null");
case String s -> System.out.println("String");
case Color c -> System.out.println("Color: " + c.toString());
case Point p -> System.out.println("Record class: " + p.toString());
case int[] ia -> System.out.println("Array of ints of length" + ia.length);
default -> System.out.println("Something else");
}
}
Dominance of Pattern labels
The String pattern label is unreachable in the sense that there is no value of the selector expression that would cause it to be chosen. By analogy to unreachable code, this is treated as a programmer error and results in a compile-time error.
switch (o) {
case CharSequence cs ->
System.out.println("A sequence of length " + cs.length());
case String s -> // Error - pattern is dominated by previous pattern
System.out.println("A string: " + s);
default -> {
break;
}
}
}
A switch expression requires that all possible values of the selector expression be handled in the switch block; in other words, it is exhaustive.
return switch (o) { // Error - still not exhaustive
case String s -> s.length();
case Integer i -> i;
};
}
Scope of pattern variable declarations
- The scope of a pattern variable declaration which occurs in a switch label includes any when clause of that label.
- The scope of a pattern variable declaration which occurs in a case label of a switch rule includes the expression, block, or throw statement that appears to the right of the arrow.
- The scope of a pattern variable declaration which occurs in a case label of a switch labeled statement group includes the block statements of the statement group. Falling through a case label that declares a pattern variable is forbidden.
if ((o instanceof String s) && s.length() > 3) {
System.out.println(s);
} else {
System.out.println("Not a string");
}
}
In the above code, the pattern variable 's' extends to all the condition evaluations facilitating the functions/operations of that type.
switch (o) {
case Character c:
if (c.charValue() == 7) {
System.out.print("Ding ");
}
if (c.charValue() == 9) {
System.out.print("Tab ");
}
System.out.println("Character");
default:
System.out.println();
}
}
The above code follows the scope rule (2).
425: Virtual Threads (Preview)
- In the current threading model, the number of available threads is limited because the JDK implements threads as wrappers around operating system (OS) threads. Consuming all the threads leads to CPU/Memory resource exhaustion.
- Asynchronous processing by thread pooling requires redesigning of code with callbacks and as requests are executed on diverse threads debugging, capturing stack traces would be difficult so as to comprehend the flow.
When code running in a virtual thread calls a blocking I/O operation in the java.* API, the runtime performs a non-blocking OS call and automatically suspends the virtual thread until it can be resumed later.
Virtual threads are cheap and plentiful, and thus should never be pooled: A new virtual thread should be created for every application task. Most virtual threads will thus be short-lived and have shallow call stacks, performing as little as a single HTTP client call or a single JDBC query. Platform threads, by contrast, are heavyweight and expensive, and thus often must be pooled. They tend to be long-lived, have deep call stacks, and be shared among many tasks.
The program first obtains an ExecutorService that will create a new virtual thread for each submitted task. It then submits 10,000 tasks and waits for all of them to be complete:
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
Virtual threads are a preview API, disabled by default
- Compile the program with javac --release 19 --enable-preview Main.java and run it with java --enable-preview Main; or,
- When using the source code launcher, run the program with java --source 19 --enable-preview Main.java; or,
- When using jshell, start it with jshell --enable-preview.
428: Structured Concurrency (Incubator)
Structured concurrency treats multiple tasks running in different threads as a single unit of work, streamlining error handling and cancellation, improving reliability, and enhancing observability.
The ExecutorService immediately returns a Future for each subtask, and executes each subtask in its own thread. The handle() method awaits the subtasks' results via blocking calls to their futures' get() methods, so the task is said to join its subtasks.
Future<String> user = esvc.submit(() -> findUser()); //subtask
Future<Integer> order = esvc.submit(() -> fetchOrder()); //subtask
String theUser = user.get(); // Join findUser
int theOrder = order.get(); // Join fetchOrder
return new Response(theUser, theOrder);
}
- If findUser() throws an exception then handle() will throw an exception when calling user.get() but fetchOrder() will continue to run in its own thread. This is a thread leak which, at best, wastes resources; at worst, the fetchOrder() thread will interfere with other tasks.
- If the thread executing handle() is interrupted, the interruption will not propagate to the subtasks. Both the findUser() and fetchOrder() threads will leak, continuing to run even after handle() has failed.
- If findUser() takes a long time to execute, but fetchOrder() fails in the meantime, then handle() will wait unnecessarily for findUser() by blocking on user.get() rather than cancelling it. Only after findUser() completes and user.get() returns will order.get() throw an exception, causing handle() to fail.
Subtasks are executed in their own threads by forking them individually and then joining them as a unit and, possibly, cancelling them as a unit. The subtasks' successful results or exceptions are aggregated and handled by the parent task. StructuredTaskScope confines the lifetimes of the subtasks, or forks, to a clear lexical scope in which all of a task's interactions with its subtasks — forking, joining, cancelling, handling errors, and composing results — takes place.
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> user = scope.fork(() -> findUser());
Future<Integer> order = scope.fork(() -> fetchOrder());
scope.join(); // Join both forks
scope.throwIfFailed(); // ... and propagate errors
// Here, both forks have succeeded, so compose their results
return new Response(user.resultNow(), order.resultNow());
}
}
Virtual threads deliver an abundance of threads. Structured concurrency ensures that they are correctly and robustly coordinated, and enables observability tools to display threads as they are understood by the developer.
424: Foreign Function and Memory API (Preview)
To interoperate with data outside the java runtime, facilitating in invoking foreign functions and safely accessing foreign memory, calling native libraries and operators on native data without JNI
426: Vector API (Fourth Incubator)
The Vector API aims to improve the situation by providing a way to write complex vector algorithms in Java, using the existing HotSpot auto-vectorizer but with a user model which makes vectorization far more predictable and robust. Hand-coded vector loops can express high-performance algorithms, such as vectorized hashCode or specialized array comparisons, which an auto-vectorizer may never optimize. Numerous domains can benefit from this explicit vector API including machine learning, linear algebra, cryptography, finance, and code within the JDK itself.
422: Linux/RISC-V Port
Platform hardware that uses an operating system based on the Reduced Instruction Set Computer (RISC) -V architecture to simplify the individual instructions given to the computer to accomplish tasks, will now have the ability for JDK features exposed via a Port. The feature aims at integrating port into JDK main-line repository.
The port will support,
- The template interpreter,
- The C1 (client) JIT compiler,
- The C2 (server) JIT compiler, and
- All current mainline GCs, including ZGC and Shenandoah.
References:
- Comments are welcome. However, the blog owner reserves the right to edit or delete any comments submitted to this blog without notice due to :
- Comments deemed to be spam or questionable spam.
- Comments including profanity.
- Comments containing language or concepts that could be deemed offensive.
- Comments containing hate speech, credible threats, or direct attacks on an individual or group.
Comments
Post a Comment