Why architecture matters here

The architecture matters because the competing solutions to JVM cold start each give something important up, and CRaC's is a different point in the trade space. Ahead-of-time native compilation (GraalVM Native Image) produces a tiny, instant-starting binary but at the cost of a closed-world assumption that breaks reflection-heavy frameworks without extensive configuration, a separate and slower build, and — critically — the loss of the JIT's ability to profile and optimize at runtime, so peak throughput can be lower than a warmed JVM. Simply over-provisioning to hide slow starts wastes money and still leaves you slow when a spike outruns your headroom. CRaC keeps the standard JVM, its dynamic features, and its runtime-profiled JIT peak performance, and simply removes the time it takes to reach that peak by capturing it once.

The performance win is specifically about time to peak, and that is what elastic systems need. A newly-restored instance serves its first request at the throughput and latency of a fully-warmed JVM, not at cold-interpreter speed climbing slowly toward warm. For autoscaling, this means added capacity is useful immediately rather than after a warm-up period during which the new instance is a liability that serves slow requests. For serverless-style short-lived invocations, it means the warm-up cost — which could dwarf the actual work — is paid once at build/checkpoint time and never again per invocation.

The coordination model matters because it is what makes checkpoint/restore correct and not merely fast. A process image naively restored would hold file descriptors pointing at files that may no longer exist, sockets to peers that have long since closed the connection, and a cached wall-clock time from whenever the checkpoint was taken. CRaC's contract — release external resources before the checkpoint, reacquire them after restore — is what turns a frozen process into a valid running one in its new context. The architecture is as much about this disciplined handoff of external state as it is about the snapshot itself.

Finally, the one-image-to-many-instances property is what makes CRaC an operational multiplier rather than a one-off trick. A single checkpoint of a warmed, healthy process becomes the launch template for every instance of that service: scale out by restoring N copies of the image, each identical, each warm, each ready in milliseconds. This shifts warm-up from a recurring per-instance runtime cost to a one-time build-pipeline cost, which is exactly the kind of amortization that makes a technique worth the added complexity — provided, as we will see, you handle the sharing of image state (especially secrets and identity) with care.

Advertisement

The architecture: every piece explained

The checkpoint is the act of capturing a running JVM's complete state — its memory (heap and stacks), its threads, and its open kernel resources — into an image on disk while the process is paused. On Linux this is implemented on top of CRIU (Checkpoint/Restore In Userspace), a kernel-assisted facility that can freeze a process tree and serialize it. The JVM cooperates with CRIU rather than being snapshotted blindly, which is what allows the coordination step to run at exactly the right moment. The output is a set of image files that fully describe the frozen process.

The restore maps that image back into a new process and resumes execution from the instruction after the checkpoint. Because the heap, loaded classes, and JIT-compiled code are all in the image, the restored process is warm the instant it resumes — there is no class loading, no interpretation phase, no re-JIT. Restore time is dominated by mapping memory and reopening resources, typically milliseconds, versus the seconds of a cold start.

The coordination API is the application-facing heart of CRaC. Components implement the Resource interface with two callbacks and register with a global Core context. Before a checkpoint, the runtime invokes beforeCheckpoint() on every registered resource in an ordered way, giving each a chance to flush and close what cannot survive the freeze — open files, sockets, connection pools, timers. After a restore, it invokes afterRestore() on each, in reverse order, so each can reopen its connections, refresh caches, and re-read anything that may have changed. Frameworks (Spring, and connection-pool and driver libraries) increasingly implement these hooks so that application code inherits correct behavior without hand-writing every handoff.

The resource registry and ordering ensure dependencies are torn down and rebuilt in a sane sequence — you close the connection pool before the sockets it uses, and reopen in the opposite order. The re-read of environment and secrets on restore is a first-class concern: because the image was created earlier and is restored later (and possibly elsewhere), values like the current time, secrets, host identity, and configuration must be re-fetched in afterRestore() rather than trusted from the frozen state. And the image itself is the shared artifact: it contains a full memory snapshot, so anything that was in memory at checkpoint time — including secrets that were loaded — is in the image, which is why image storage and the timing of secret loading matter enormously. These pieces — CRIU-based checkpoint, fast restore, the before/after coordination callbacks, ordered resource handling, environment/secret re-read, and the shareable image — are the complete architecture.

CRaC — Coordinated Restore at Checkpoint: snapshot a warmed-up JVM, restore it in millisecondscheckpoint a running, JIT-warmed process to an image; later restore many instances from it, near-instantlyRunning JVMwarmed heap + JIT codebeforeCheckpoint()resources release hookCRIU / checkpointprocess image to diskCheckpoint imagememory + threads + stateRestoremap image, resumeafterRestore()reopen resources hookRestored JVMwarm from millisecond 0New env / secretsre-read on restoreFresh sockets / filesreopened, not staleOps — image contains secrets, clock/randomness jump, one image → many warm instancesnotifyfreeze→ imagewriteloadnotifyresumereloadreopengovern
CRaC checkpoints a fully warmed-up JVM — heap populated, classes loaded, JIT-compiled hot code in place — into a process image on disk. Before the freeze, registered resources release file handles and sockets via beforeCheckpoint(); after a restore, afterRestore() reopens them and the app re-reads environment and secrets. A single checkpoint image can restore into many instances, each running at peak-warm performance from the first request instead of enduring a cold JVM warm-up.
Advertisement

End-to-end flow

Walk the lifecycle of a CRaC-enabled service. In the build or a dedicated warm-up stage, the application is started on a CRaC-capable JVM and then exercised: a warm-up script drives representative traffic so classes load, caches populate, and the JIT compiles the hot paths to optimized native code. The process is now at peak performance — exactly the state we want to capture. This warm-up is the one-time cost, paid in the pipeline, not on every instance.

Now the checkpoint is triggered (via the JVM's checkpoint command or an API call). The runtime invokes beforeCheckpoint() on every registered resource in order: the HTTP server stops accepting connections and closes its listen socket, the database pool drains and closes its connections, open files are flushed and closed, background timers are cancelled. With external resources released, CRIU freezes the process and writes the image — the warm heap, the loaded classes, the compiled code, the thread states — to disk. The image is stored as the launch artifact for this service version.

At scale-out time, the orchestrator restores an instance from the image. CRIU maps the image into a new process and execution resumes right after the checkpoint point. The runtime immediately invokes afterRestore() on each resource, in reverse order: the app re-reads its environment and fetches fresh secrets (the ones baked into the image are treated as stale), the database pool opens new connections to the current database endpoint, the HTTP server reopens its listen socket and begins accepting traffic. Within milliseconds the instance is serving requests at fully-warmed throughput — no cold-start ramp. A dozen such restores from the same image give a dozen warm instances, each having re-established its own live connections and re-read its own current configuration.

Consider the correctness details this flow depends on. The wall-clock time inside the restored process would, without care, reflect the checkpoint moment; CRaC restores update the notion of time so timers and timestamps do not think it is still last week, but application code that cached 'now' at startup must refresh it in afterRestore(). Any random seed or unique identifier generated at startup and captured in the image would be identical across all restored instances — a security and correctness hazard — so such values must be regenerated after restore. Connections captured in the image would be dead; that is exactly why they were closed before checkpoint and reopened after. The discipline of the coordination callbacks is not boilerplate — it is the mechanism that reconciles a frozen snapshot of the past with the live reality of the present, and it is what separates a CRaC deployment that works from one that resumes into subtle, dangerous inconsistency.