Project Leyden & JDK 26: Bringing AOT Caching to ZGC

Tue, 03 Feb 2026 11:57:00 +0000
Java, JVM, Application Performance, Project Leyden

Project Leyden is an ongoing initiative aimed at improving the startup and warmup times of the Java Virtual Machine (JVM). Building on the foundations laid by JEP 483, JEP 514, and JEP 515, the upcoming JDK 26 release will introduce JEP 516: Ahead-of-Time (AOT) Object Caching with Any GC. In this article, I explain the specific problems this feature addresses.

The problem

Until now, Project Leyden did not support the Z Garbage Collector (ZGC). ZGC is a low-latency collector designed to keep application pause times under a millisecond, even when managing massive heaps of up to 16TB.

The incompatibility stems from how ZGC handles object references compared to other GC implementations. ZGC uses a technique called colored pointers, where it encodes metadata bits directly into the object reference. This allows the collector to perform its work concurrently while the application is running.

Previous iterations of the AOT cache stored object references as literal memory addresses. Because ZGC's unique reference format differs so fundamentally from standard pointers, the existing AOT cache was incompatible.

The solution

To bridge this gap, JEP 516 introduces the ability to store object references as logical indices rather than physical memory addresses. At application startup, the JVM reads these objects from the cache and streams them into memory, remapping the indices to the correct memory addresses for the current environment.

This introduces a distinction between two types of caches:

GC-specific cache: maps cached objects directly into memory

GC-agnostic cache: streams objects into memory

The JVM uses heuristics to determine which format to use during the training run:

1. GC-specific cache is used if -XX:+UseCompressedOops is enabled (the default for heaps under 32GB). The assumption here is that the production environment will mirror the training environment - specifically, that the heap will remain under 32GB and ZGC will not be used.

2. GC-agnostic cache is automatically triggered if any of the following conditions are met during training:

ZGC is active

-XX:-UseCompressedOops is explicitly set

The heap size exceeds 32GB

You can also manually force the creation of a streamable cache even when CompressedOops is on by using: -XX:+AOTStreamableObjects (it must be preceded by the-XX:+UnlockDiagnosticVMOptions option).

The UseCompressedOops (Ordinary Object Pointers) option determines the size of object references: 32-bit when enabled and 64-bit when disabled. Since ZGC requires 64-bit (uncompressed) pointers to store its metadata, it is fundamentally incompatible with the 32-bit GC-specific cache.

Because of this new GC-agnostic format, you can train a cache with one collector and deploy it with another. For example, you can create a cache using the default G1GC (while disabling compressed pointers):

java -XX:AOTCacheOutput=cache.aot -XX:-UseCompressedOops -jar example.jar

And then seamlessly run that same application using ZGC:

java -XX:AOTCache=cache.aot -XX:+UseZGC -jar example.jar

Testing

To evaluate the difference between memory-mapped (GC-specific) and streamable (GC-agnostic) AOT caches, I conducted tests on a Google Cloud Platform (GCP) VM running Debian GNU/Linux 12 (bookworm) on an Intel Xeon @ 2.20GHz (4 vCPUs, 16GB RAM).

For these tests, I used the latest (as of 31.01.2026)...