Backend Development 10 min read

Exploring Java 21: Upgrade Insights, Generational ZGC, and Virtual Threads

This article reviews Oracle's Java 21 LTS release, outlines its new JEP features, compares it with Java 17, shares upgrade experiences including pom updates and runtime fixes, and evaluates generational ZGC and virtual threads with performance tests and code examples.

JD Cloud Developers
JD Cloud Developers
JD Cloud Developers
Exploring Java 21: Upgrade Insights, Generational ZGC, and Virtual Threads

Introduction

Oracle released Java 21 as the latest LTS version, prompting the author to upgrade a personal project and share the findings.

Java 21 Update Overview

Official release notes and community introductions are linked for further reading.

New Features

JEP 431: Sequenced Collections

JEP 439: Generational ZGC

JEP 440: Record Patterns

JEP 441: Switch Pattern Matching

JEP 444: Virtual Threads

JEP 449: Deprecate Windows x86

JEP 451: Prepare to Disallow Dynamic Proxy Loading

JEP 452: Key Encapsulation Mechanism API

JEP 430: String Templates (Preview)

JEP 442: Foreign Function and Memory API (Third Preview)

JEP 443: Unnamed Patterns and Variables (Preview)

JEP 445: Unnamed Classes and Instance Main Methods (Preview)

JEP 446: Scoped Values (Preview)

JEP 453: Structured Concurrency (Preview)

JEP 448: Vector API (Incubator 6)

Comparison with Java 17

The JDK binary size grew from 289 MB to 320 MB, the module count decreased by one, and the packaging metaphor changed from stainless steel to titanium. The following images illustrate the differences:

Upgrade Experience

Download links:

OpenJDK: https://jdk.java.net/21/

Oracle: https://www.oracle.com/java/technologies/downloads/

After updating the

pom.xml

, the project compiled but failed at runtime with the error:

<code>java.lang.NoSuchFieldError: Class com.sun.tools.javac.tree.JCTree$JCImport does not have member field 'com.sun.tools.javac.tree.JCTree qualid'</code>

Upgrading Lombok to version 1.18.30 resolved the issue.

Compatibility check revealed a charset problem with

PopupMenu

used in the system tray.

Generational ZGC Exploration

Generational ZGC reduces allocation stalls, heap memory overhead, and GC CPU overhead. It can be enabled with the JVM options

-XX:+UseZGC -XX:+ZGenerational

. Preliminary memory usage measured with the MooInfo tool is shown below:

Virtual Thread Exploration

Virtual threads are lightweight threads that simplify writing, maintaining, and debugging high‑throughput concurrent applications. They differ from platform threads, which are thin wrappers over OS threads.

Key points:

Implementation is analogous to virtual memory mapping.

Many virtual threads are multiplexed onto a few OS threads.

Virtual threads typically have shallow call stacks and are suited for I/O‑bound tasks.

Although they support thread‑local variables, a single JVM can host millions of virtual threads.

Sample code for creating and using virtual threads:

<code>Thread thread = Thread.ofVirtual().start(() -> System.out.println("Hello"));
thread.join();</code>
<code>try {
    Thread.Builder builder = Thread.ofVirtual().name("MyThread");
    Runnable task = () -> System.out.println("Running thread");
    Thread t = builder.start(task);
    System.out.println("Thread t name: " + t.getName());
    t.join();
} catch (InterruptedException e) {
    e.printStackTrace();
}</code>
<code>public class CreateNamedThreadsWithBuilders {
    public static void main(String[] args) {
        try {
            Thread.Builder builder = Thread.ofVirtual().name("worker-", 0);
            Runnable task = () -> System.out.println("Thread ID: " + Thread.currentThread().threadId());
            Thread t1 = builder.start(task);
            t1.join();
            System.out.println(t1.getName() + " terminated");
            Thread t2 = builder.start(task);
            t2.join();
            System.out.println(t2.getName() + " terminated");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}</code>

Using the virtual‑thread‑per‑task executor:

<code>try (ExecutorService myExecutor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<?> future = myExecutor.submit(() -> System.out.println("Running thread"));
    future.get();
    System.out.println("Task completed");
} catch (InterruptedException | ExecutionException e) {
    e.printStackTrace();
}</code>

Performance Comparison

A simple benchmark compared 100 virtual threads versus 100 platform threads for I/O‑bound file reads and HTTP GET requests to Baidu. Virtual threads showed clear advantages for I/O‑bound workloads, while CPU‑bound tasks exhibited similar performance.

Record Patterns

Java 21 introduces record patterns, allowing concise deconstruction of record components.

<code>static void printSum(Object obj) {
    if (obj instanceof Point(int x, int y)) {
        System.out.println(x + y);
    }
}
</code>

Reference material and further reading links are provided at the end of the article.

JavaperformanceZGCVirtualThreadsRecordPatternsJava21
JD Cloud Developers
Written by

JD Cloud Developers

JD Cloud Developers (Developer of JD Technology) is a JD Technology Group platform offering technical sharing and communication for AI, cloud computing, IoT and related developers. It publishes JD product technical information, industry content, and tech event news. Embrace technology and partner with developers to envision the future.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.