Why Upgrading to JDK 17 with ZGC Can Slash Costs and Boost Performance

This article explains how moving from Java 8 to JDK 17—leveraging new language features, updated APIs, and the ZGC garbage collector—can dramatically improve service latency, reduce GC pauses, lower machine costs, and enable AI SDKs that require Java 17, while also detailing migration steps and performance results.

Sanyou's Java Diary
Sanyou's Java Diary
Sanyou's Java Diary
Why Upgrading to JDK 17 with ZGC Can Slash Costs and Boost Performance

Java 8 remains the default runtime for many new projects, but performance bottlenecks in core services often cannot be solved by JVM tuning alone; upgrading to JDK 17 with ZGC dramatically improves latency, stability and reduces machine cost by about 10%.

1. JDK 17 Main Features

1.1 Language Features

1.1.1 Local Variable Type Inference

Using var allows concise declarations without explicit types.

// JDK 8
String str = "Hello world";

// JDK 17
var str = "Hello world";

1.1.2 Sealed Classes

Sealed classes restrict inheritance to a predefined set of subclasses.

public sealed class Shape permits Circle, Rectangle, Triangle {
    // ...
}
public final class Circle extends Shape { }
public non-sealed class Square extends Shape { }
public sealed class Rectangle permits FilledRectangle { }

1.1.3 Record Classes

Records provide immutable data carriers with automatically generated equals(), hashCode() and toString().

record Rectangle(double length, double width) { }

1.1.4 Switch Expression Improvements

Switch expressions can return values without break statements.

// JDK 8
switch (day) {
    case MONDAY: numLetters = 6; break;
    // ...
}

// JDK 17
int num = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
    default -> throw new IllegalStateException("Invalid day: " + day);
};

1.1.5 Text Blocks

Multi‑line strings can be written without escape sequences.

// JDK 8
String sql = "SELECT * FROM user WHERE name='John'
" +
             "AND age>30";

// JDK 17
String sql = """
    SELECT * FROM user
    WHERE name='John'
    AND age>30
""";

1.2 New APIs and Tools

1.2.1 HttpClient

The modern HttpClient API replaces third‑party HTTP libraries.

HttpClient client = HttpClient.newBuilder()
    .version(HttpClient.Version.HTTP_1_1)
    .followRedirects(HttpClient.Redirect.NORMAL)
    .connectTimeout(Duration.ofSeconds(20))
    .build();
HttpResponse<String> resp = client.send(request, BodyHandlers.ofString());

1.2.2 jpackage

jpackage creates native installers (exe, dmg) that bundle the JDK runtime, eliminating the need for a separate JDK installation.

1.2.3 Process API Enhancements

ProcessHandle provides richer process creation and monitoring capabilities.

ProcessBuilder pb = new ProcessBuilder("echo", "Hello World!");
Process p = pb.start();
System.out.println("PID: " + p.pid());

1.3 Performance Optimizations & Bug Fixes

1.3.1 ZGC Garbage Collector

Supports terabyte‑scale heaps

Pauses under 10 ms

Throughput impact < 15 %

On a 128 GB machine ZGC outperforms G1 by 30 % and reduces pause time by 99 %.

1.3.2 NIO Rewrites

Unix‑Domain sockets supported natively

File‑channel memory‑mapping for fast I/O

Zero‑copy transfers

1.3.3 Java Platform Module System (JPMS)

Modularization reduces startup memory (no longer loads full rt.jar) and improves encapsulation.

1.3.4 Java Agent Attach Bug Fix

JDK 17 automatically recreates the attach socket file, avoiding the need for a JVM restart.

1.3.5 Elastic Metaspace

Unused metaspace memory is reclaimed more promptly, lowering overall memory usage.

2. JDK 17 + ZGC in the Security Domain

2.1 Meituan JDK Landscape

Production primarily runs Oracle JDK 8u201, with OpenJDK 17 and a few OpenJDK 11 instances.

2.2 ZGC Applicability

Server count > 100, each > 16 C/16 G, heap > 16 G

CPU utilization peaks around 50 %

High GC share in flame graphs

Frequent alerts during peak load

2.3 ZGC Effects

2.3.1 Performance Benchmarks

TP9999 reduced by 220‑380 ms (‑18 % ~ ‑74 %).

TP999 reduced by 60‑125 ms (‑10 % ~ ‑63 %).

TP99 reduced by 3‑20 ms (‑0 % ~ ‑25 %).

2.3.2 Case 1 – Intelligent Decision System (JDK 11 → JDK 17)

CPU busy dropped from 47.86 % to 41.49 % after upgrade.

Long‑run TP9999 remained stable over 15 days, while JDK 11 showed gradual degradation.

Service error count fell from a peak of 6000 to 349.

JVM metaspace usage decreased noticeably.

2.3.3 Case 2 – Content‑Security Core Service (JDK 8 → JDK 17)

Original service used CMS GC with 17 ms average Young GC pauses and 6 GC/minute.

After switching to ZGC, TP9999 latency dropped by ~17 ms, error spikes reduced, and CPU usage rose only ~10 % at peak.

2.4 ZGC Implementation Overview

ZGC follows a mark‑copy algorithm where the mark, transfer and relocation phases are almost entirely concurrent, limiting stop‑the‑world pauses to three short phases (initial mark, final mark, initial transfer) that scale with the number of GC roots, not heap size.

2.4.1 Key Characteristics

Single‑generation (no Young GC) in JDK 17; JDK 21 adds generational support.

Region‑based heap with flexible region sizes.

Partial compaction via region‑wise “mark‑compact”.

NUMA‑aware allocation.

Colored pointers store GC metadata, avoiding object‑header overhead.

Read barriers ensure correctness during concurrent marking and relocation.

3. JDK 17 Upgrade Practice

3.1 Installation & Compatibility

Typical error:

module java.base does not "opens java.util.concurrent.locks" to unnamed module

caused by JPMS encapsulation.

Resolution: add the required --add-opens JVM arguments.

--add-opens java.base/java.util.concurrent.locks=ALL-UNNAMED
--add-opens java.base/java.lang=ALL-UNNAMED
... (other modules as needed)

3.2 Performance Testing

Baseline: JDK 8 + CMS.

Method: run experiments on identical hardware, restart between runs, average results.

Metrics: peak/average CPU, TP9999, error count, GC time/count, heap & metaspace usage, flame graphs.

3.3 JVM Parameters for Production

-Xmx12g -Xms12g
-XX:MaxDirectMemorySize=460m
-XX:+HeapDumpOnOutOfMemoryError
-XX:ReservedCodeCacheSize=256m -XX:InitialCodeCacheSize=256m
-XX:+UseZGC
-XX:ZAllocationSpikeTolerance=1
-XX:ZCollectionInterval=130
-XX:ConcGCThreads=3
-XX:ParallelGCThreads=8
-XX:ZStatisticsInterval=130
-XX:+PrintGCDetails
-Xlog:safepoint,class+load=info,class+unload=info,classhisto*=trace,age*,gc*=info:file=/opt/logs/gc-%t.log:time,tid,tags:filecount=5,filesize=50m
--add-opens java.base/java.lang=ALL-UNNAMED ...

4. Conclusion

ZGC delivers superior latency and cost benefits; upgrading yields noticeable performance gains.

Spring AI SDK requires at least JDK 17, making the upgrade essential for AI initiatives.

Direct migration from JDK 8 to JDK 17 may involve many compatibility fixes; a staged upgrade via JDK 11 can ease the transition.

AI large‑model assistance can help resolve migration challenges faster.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

zgcUpgradeGarbageCollectionJDK17JavaPerformance
Sanyou's Java Diary
Written by

Sanyou's Java Diary

Passionate about technology, though not great at solving problems; eager to share, never tire of learning!

0 followers
Reader feedback

How this landed with the community

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.