Beyond Cold Starts: Operating Restored Java Services with OpenJ9 CRIU Support

Checkpoint/restore can do more than reduce startup and warm-up time. OpenJ9 CRIU support lets Java application teams use a warmed JVM as a reusable runtime baseline while retaining standard JVM capabilities for diagnostics and incident response after restore.

Checkpoint/restore is often presented as a cold-start optimization: initialize a Java service once, capture its state, then restore ready-to-run instances when demand arrives. That is valuable for elastic workloads, but it is only part of the operational story.

A restored JVM is an application resumed from a known point in its lifecycle. It may already have loaded classes, initialized its framework, populated caches, and compiled hot methods. The relevant operational questions therefore change: can an operator enable additional diagnostics for one restore? Can a warmed instance be investigated without repeating a cold start?

OpenJ9 CRIU support is the JVM-level checkpoint/restore capability discussed in this article. It is exposed through the CRIUSupport API and underpins InstantOn-style deployment workflows, including Open Liberty InstantOn. This article focuses on the OpenJ9 CRIU API and restore-time JVM behaviour. CRIUSupport does not turn a checkpoint image into a universally portable binary, and it does not remove the need to manage application-owned resources. Instead, it offers a more useful model: a warmed Java runtime baseline whose restore-time behaviour can still be controlled responsibly.

A checkpoint image is a baseline, not a finished deployment

An image created in one environment is not automatically ready for every other environment. The restore environment might use a different diagnostics profile, container limit, shutdown model, or incident-response policy.

The useful mental model is therefore:

Warm once; restore with deliberate operational controls.

Checkpoint/restore lifecycle. During the build phase, the application is initialized and ideally exercised with production-like load. Checkpoint hooks run before the warmed JVM state is captured. In production, OpenJ9 restores the image, reads any supported restore-time options, runs the restore hooks, and resumes the application so that it can become ready for traffic.

The checkpoint image contains reusable runtime state. Restore-time inputs and restore hooks determine how that state should operate in the target environment.

Restore-time controls in OpenJ9

The application registers only the paths where OpenJ9 should look for restore-time inputs. The referenced files do not have to exist when the checkpoint is taken; they can be created, replaced, or updated in the target environment before the checkpoint image is restored.

The application can register:

  • An options file containing supported restore-time JVM options.
  • An environment file containing environment variables to add after restore. The special OPENJ9_RESTORE_JAVA_OPTIONS variable can also supply supported JVM options.
import java.nio.file.Path;
import org.eclipse.openj9.criu.CRIUSupport;

CRIUSupport criu = CRIUSupport.getCRIUSupport()
    .setImageDir(Path.of("/opt/checkpoint"))
    .registerRestoreOptionsFile(Path.of("/opt/runtime/restore.options"))
    .registerRestoreEnvFile(Path.of("/opt/runtime/restore.env"));

criu.checkpointJVM();

Applications do not necessarily need code changes to register these paths. OpenJ9 also recognizes system properties for the restore-options and environment files:

-Dopenj9.internal.criu.optionsFile=/opt/runtime/restore.options \
-Dopenj9.internal.criu.envFile=/opt/runtime/restore.env

Whether the paths are supplied through the CRIUSupport API or through system properties, they tell OpenJ9 where to look at restore time. The referenced files can be absent when the checkpoint is created and supplied later in the restore environment.

# /opt/runtime/restore.options
-Xshareclasses:disableOnRestore
-Xtrace:print={j9vm}

# /opt/runtime/restore.env
APP_CONFIG_FILE=/etc/myapp/region-a.properties
OPENJ9_RESTORE_JAVA_OPTIONS=-Xshareclasses:disableOnRestore -Xtrace:print={j9vm}

Environment-file boundary: registerRestoreEnvFile is additive. Environment variables captured in the checkpointed JVM are retained. Entries in the restore environment file are added only when they do not modify an existing variable, so the file is not a general environment-override mechanism.

Only a defined subset of JVM options can be supplied after restore. Options that establish fundamental VM state, such as the GC policy, compressed-reference mode, heap-region layout, or arbitrary new -Xms and -Xmx values, cannot generally be changed after that state has been captured.

Which controls are useful after restore?

Operational need Relevant control Restore-time behaviour and boundary
Stop further shared-class-cache use -Xshareclasses:disableOnRestore Disables further use of the shared classes cache after restore. It does not remove classes or compiled code already captured in the checkpoint image.
Adjust signal handling -Xrs:onRestore
-Xrs:syncOnRestore
Reduces JVM signal handling after restore. Because signal handlers or related state might already be present in the image, validate the behaviour with the container runtime, health probes, diagnostics, and shutdown process.
Enable verbose GC logging -Xverbosegclog Enables verbose GC logging for the restored JVM, allowing GC activity to be observed in the target environment.
Add or modify tracing -Xtrace Applies tracing settings after restore. A trace or JIT log file configured before checkpoint must still exist and remain unchanged at restore. Method tracing might also be inconsistent for methods compiled before checkpoint.
Configure dump agents -Xdump Applies dump-agent configuration after restore. Dump events triggered by exception throws or catches cannot be enabled at this stage.
Change GC parallelism -Xgcthreads The GC-thread count captured at checkpoint is the minimum. A lower restore-time value is ignored; a higher value can increase GC parallelism for the target environment.
Adjust JIT compilation concurrency -XcompilationThreads Changes the number of JIT compilation threads used after restore, allowing compilation activity to be aligned with the CPU capacity of the target environment.
Limit JIT sampling duration -XsamplingExpirationTime Controls when the JIT sampling thread stops. After the sampling thread has been disabled, it cannot be restarted.
Report code-cache use -XX:+PrintCodeCache
-XX:-PrintCodeCache
Controls whether code-cache usage is printed when the restored JVM exits.
Refine JIT and AOT behaviour Supported -Xjit and -Xaot parameters Supported parameters include count, limit, limitFile, exclude, AOT loading filters, verbose, and vlog. Some affect only future compilation or AOT loading, while selected filters can invalidate compiled methods that no longer match.
Disable future AOT activity -Xnoaot Prevents future AOT compilation and loading. It does not affect compiled methods already captured in the checkpoint and does not prevent JIT compilation.
Disable future JIT compilation -Xnojit Invalidates existing compiled methods and prevents future JIT compilation. It does not prevent AOT compilation or loading.
Debug a restored JVM JDWP, suspendOnRestore, and -XX:+DebugOnRestore JDWP can be enabled before checkpoint, with suspendOnRestore controlling suspension after restore. Alternatively, a JVM started with -XX:+DebugOnRestore can accept JDWP configuration through the restore-time options.

The JIT compiler cannot always be re-enabled after restore. If the JVM was checkpointed with -Xint, or with both JIT and AOT disabled, restore-time options do not recreate the compiler state that was absent from the checkpoint. Similarly, supplying -Xaot after restore does not re-enable AOT if the checkpoint was created with -Xnoaot.

GC and heap controls selected before checkpoint

Some controls influence the restored JVM but cannot be newly supplied through the restore-options file. They must be selected when the checkpoint image is created.

Operational need Checkpoint-time control Effect after restore
Reduce GC-thread restoration overhead -XX:CheckpointGCThreads=<number> Reduces the number of GC threads retained when the checkpoint image is created. At restore, OpenJ9 can increase the count using a restore-time -Xgcthreads value or the default appropriate for the target environment, but it cannot reduce the count below the checkpoint value.
Adapt heap limits to available memory -XdynamicHeapAdjustment Allows OpenJ9 to adjust the effective minimum and maximum heap sizes so that they remain within the physical-memory limits of the checkpoint and restore environments. The option must be enabled at checkpoint and applies to both checkpoint and restore.

Without dynamic heap adjustment, the heap size and heap regions established at checkpoint remain part of the restored JVM state. For example, an image created in a container without a memory limit does not automatically adopt a smaller limit when it is restored in another container.

-XdynamicHeapAdjustment provides an opt-in mechanism for checkpoint images that might be restored into environments with different memory capacities. It does not turn restore into a second JVM startup or permit arbitrary replacement of every heap and GC setting.

This distinction is important: restore-time configuration is a controlled adjustment of an already-running JVM, not a second JVM command line.

Debugging a restored JVM

A fault might appear only after the application is warm, a cache has been populated, or a particular restore environment is active. OpenJ9 allows a debugger to be attached after the checkpoint image is restored.

One approach is to configure JDWP before checkpoint and use the OpenJ9-specific suspendOnRestore parameter to control whether the restored application waits for the debugger:

-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=127.0.0.1:5005,suspendOnRestore=y

Here, suspend=n allows the application to run normally before checkpoint, while suspendOnRestore=y suspends it after restore until the debugger is connected and ready.

Alternatively, when the JVM is started with -XX:+DebugOnRestore, the JDWP configuration can be supplied through the restore-time options so that debugging is enabled for the restored JVM.

After restore, the debugger is attached through the standard Java JDWP workflow. As with any JDWP configuration, expose the debug connection only in an appropriately secured environment.

Conclusion: fast startup with operational control

Checkpoint/restore changes the unit of deployment. Instead of deploying only application code and a cold JVM, teams can deploy a warmed runtime baseline together with controlled restore-time configuration.

OpenJ9 CRIU support is therefore not only about reaching readiness sooner. It retains selected JVM tuning, diagnostic, and debugging capabilities when a warmed Java process becomes a reusable deployment artifact.

References

  1. OpenJ9 CRIU support
  2. OpenJ9 JDK 26 CRIUSupport API
  3. OpenJ9 -Xrunjdwp and suspendOnRestore
  4. OpenJ9 compiler support for Debug On Restore
  5. OpenJ9 -XX:CheckpointGCThreads
  6. OpenJ9 -XdynamicHeapAdjustment

 

 

 

 

Total
0
Shares
Previous Post

Connecting Java Reinforcement Learning to Python Gymnasium

Next Post

07-2026 | Engineering Decisions

Related Posts