OpenJDK 21 was the default OpenJDK since Noble. In Ubuntu 26.04, we moved to OpenJDK 25. The post below examines performance changes in Ubuntu between OpenJDK 21 and 25.
Enhancements since Java 21 in the Java 25 release
The default garbage collector (G1) enhancements improve resource consumption and speed of Java programs. “Late Barrier Expansion for G1” (JEP 475[1]) decreases the workload of the C2 (HotSpot optimizing compiler) by reducing the number of nodes it needs to process. “Region Pinning for G1” (JEP 423[2]) improves interaction between Java and native code — the garbage collector is no longer disabled during Java Native Interface critical regions when native code accesses objects from the Java heap.
ZGC, a latency-oriented garbage collector, also received an improvement: “Generational ZGC” (JEP 439[3]) addresses allocation stalls that occurred when an application allocated objects faster than ZGC could reclaim them during a full scan of the heap. It also provides better throughput for applications that require a consistently low-latency garbage collector.
An opt-in feature, “Compact Object Headers” (JEP 519[4]), reduces the memory footprint and improves the performance of applications that create a large number of objects (think JSON parsers) but do not cause lock contention.
Two more opt-in features, “Ahead-of-Time Class Loading and Linking” (JEP 483[5]) and “Ahead-of-Time Method Profiling” (JEP 515[6]), build upon existing class data sharing functionality and provide much faster warmup and startup times by storing internal representations of classes and method execution statistics.
Java 25 also focuses on performance tooling. The Java Flight Recorder is now able to capture method timings and trace them via bytecode instrumentation (JEP 520[7]). This is a real game-changer for investigating production performance issues — exact metrics can be obtained without attaching a third-party profiling agent.
What does it mean for application users?
System tests
To quantify the change for application users, we have run the DaCapo[8] and Renaissance[9] benchmarks using the Phoronix Test Suite[10] on Ubuntu 26.04 with OpenJDK 25, Ubuntu 26.04 with OpenJDK 21, and Ubuntu 24.04 with OpenJDK 21. The test profiles and the suite itself are available on GitHub[11]. The results are available on OpenBenchmarking.org[12].
The most notable changes are in memory-heavy multithreaded benchmarks:
The Apache Spark Bayes benchmark[13] puts a heavy load on the garbage collector.
The Genetic Algorithm benchmark[13] uses Jenetics library with Java Futures and shows improvements in thread contention and task parallelism.
Another notable result is an outlier, showing a massive 41.7% performance improvement over Ubuntu 24.04, but this is primarily due to the Ubuntu 26.04 upgrade rather than the upgrade to OpenJDK 25.
The ALS Movie Lens benchmark[13] calls heavily into the OpenBLAS library which had a number of performance improvements between 0.3.26 in Ubuntu 24.04 and 0.3.31 in Ubuntu 26.04.
Spring Boot startup
The startup improvements were measured using the Spring Petclinic sample application[14]. The benchmark measured the time from the start of the Petclinic process to the “Started” line in the application log. The chart below shows the relative startup performance for the application built with Java releases 21 and 25.
The most notable startup improvement comes from the use of the AOT cache ― over 150%.
Web application throughput
Throughput tests were done using the AcmeAir sample application with a MongoDB backend[16], Apache JMeter[17], and the AcmeAir test suite[18]. AcmeAir simulates the core business logic of a fictitious airline — user authentication, flight searches, and real-time booking transactions.
Java 25 delivers a significant throughput boost for web applications. The locking overhead of Compact Object Headers impedes performance in this scenario, while the generational Z Garbage Collector excels when the objective is to achieve the highest possible number of transactions per second.
Compact Object Headers
We have tested the Compact Object Headers feature using a JSON deserialization benchmark[19] with the Jackson and Gson libraries. The chart below shows the performance improvement of the Jackson[20] and Gson[21] libraries with the -XX:+UseCompactObjectHeaders option:
Enabling Compact Object Headers will normally increase application performance, but there is a class of parallel algorithms that may degrade due to this change. The chart below shows performance gains and losses for some benchmarks in the Renaissance[9] test suite:
The Dining Philosophers Renaissance benchmark[13] tests lock contention and short-lived critical sections. Classic inflated locking stored the ObjectMonitor pointer directly in the markWord[22]. Compact Object Headers shrink the markWord, making this impossible, so the JVM instead uses a separate ObjectMonitorTable to map objects to their monitors[23]. It contained a regression[24] that affects performance on the philosophers and scala-dotty benchmarks. OpenJDK 27 will ship with a new implementation of ObjectMonitorTable that allows fine-tuning of lock parameters [25]. OpenJDK 27 uses ObjectMonitorTable locking mode by default in OpenJDK 27[26].
OpenJDK 25 CRaC package
OpenJDK 25 with CRaC (Coordinated Restore at Checkpoint), available as the openjdk-25-crac package, introduces a dramatic startup performance improvement for Java applications by allowing a running JVM to checkpoint its state — including JIT-compiled code, loaded classes, and initialized heap — into files on disk, which can then be restored almost instantaneously. For example, the Spring PetClinic application’s startup drops from seconds to milliseconds with CRaC. Unlike ahead-of-time approaches such as GraalVM Native Image, CRaC preserves the full capabilities of the HotSpot JVM — including dynamic class loading, reflection, and peak throughput from tiered JIT compilation. However, CRaC is not a drop-in solution: applications must be adapted to properly manage external resources such as open files, sockets, and database connections, closing or releasing them before checkpoint and re-establishing them upon restore, which requires deliberate code changes. Despite this trade-off, CRaC offers a compelling path to near-instant startup for Java workloads in containerized, serverless, and microservice environments.
Conclusions
The adoption of OpenJDK 25 in Ubuntu 26.04 as the default provides significant and measurable performance improvements in multiple real-world scenarios. Opt-in features such as Compact Object Headers provide memory and performance benefits in the right scenarios. The improved Java Flight Recorder enables accurate capture of performance metrics in production environments.
Ubuntu 26.04 promises to be an exceptional release, and we are incredibly looking forward to bringing these performance benefits to the community.
References
[1] https://openjdk.org/jeps/475
[2] https://openjdk.org/jeps/423
[3] https://openjdk.org/jeps/439
[4] https://openjdk.org/jeps/519
[5] https://openjdk.org/jeps/483
[6] https://openjdk.org/jeps/515
[7] https://openjdk.org/jeps/520
[8] https://www.dacapobench.org/
[10] https://www.phoronix-test-suite.com/
[11] https://github.com/canonical/ubuntu-pts-selection/tree/java
[12] https://openbenchmarking.org/result/2603158-NE-JAVACOMPA57&hnr=1&hni=1&hlc=1&src=1&sgm=1&nor=1&ppt=D
[13] https://renaissance.dev/resources/docs/renaissance-suite.pdf
[14] https://github.com/spring-projects/spring-petclinic/tree/edf4db28affcc4741c79850a3d95bc3f177b5ff9
[15] https://visualvm.github.io/index.html
[16] https://github.com/vpa1977/acmeair-monolithic-java/tree/chiselled-demo-17
[17] https://jmeter.apache.org/
[18] https://github.com/vpa1977/acmeair-monolithic-java/blob/chiselled-demo-17/jmeter/AcmeAir-v5.jmx
[19] https://github.com/fabienrenaud/java-json-benchmark#users-model
[20] https://github.com/FasterXML/jackson
[21] https://github.com/google/gson
[22] https://wiki.openjdk.org/spaces/HotSpot/pages/138215471/Synchronization+Using+The+ObjectMonitorTable
[24] https://bugs.openjdk.org/browse/JDK-8373595






