Prompt and applicable context
You own a Java service that starts frequently and sees changing peak traffic. The interviewer asks you to evaluate JDK 25 AOT method profiling: a training run collects method-execution profiles, and a production startup places those profiles in an AOT cache so the JIT can compile hot methods earlier. Explain the baseline, training workload, cache rollout, and rollback plan.
This tests JVM performance diagnosis and release engineering, not memorization of one flag. Assume the service continues online profiling in production and that training inputs can differ from production inputs.
What the interviewer evaluates
- Whether you distinguish an AOT cache, an AOT profile, and compiling Java methods into fixed native code.
- Whether you can explain why training data can be unrepresentative and how production-shaped traffic reduces that risk.
- Whether you can prove warmup improvement with repeatable metrics instead of one lucky cold start.
- Whether you can define cache boundaries for JDK updates, hardware, class paths, and rollback.
A weak answer says “warmup gets faster.” A strong answer explains how profiles influence the JIT, why production keeps profiling, how the cache is tested, and when to abandon it.
Clarifying questions before answering
- Is this a short-lived function, a new instance during a rolling deploy, or a long-running process? Lifetime determines whether warmup cost matters.
- Does production have stable hot paths? If request types are highly random, one training profile may provide little value.
- Are the JDK, CPU architecture, launch flags, and class path identical between training and production? A mismatch requires isolation or rebuilding.
- Is the goal first-request latency, time to steady throughput, or total CPU cost? The target changes the stop criteria.
30-second answer framework
“I would keep a cold-start and steady-state baseline without an AOT profile, then train with production-shaped representative traffic and generate a cache. During canary rollout I would track first-request latency, time to steady throughput, p50/p95/p99, CPU, RSS, errors, and cache-build cost. JDK 25 profiles let the JIT work earlier and with better evidence; production still profiles online, so I would bind the cache to the JDK, image, hardware, and class path. If the gain is unstable or we see deoptimization, p99, or memory regressions, I would disable the cache and return to an image without it.”
Step-by-step deep answer
1. Establish a comparable baseline
Pin the JDK 25 patch version, container image, CPU quota, heap settings, and class path. Run at least three groups: no AOT cache, a cache containing only class loading and linking data, and a cache containing method profiles. Repeat cold-start and steady-state experiments. Record time to ready, first request, time to target throughput, p50/p95/p99, CPU time, RSS, JIT compilation volume, and errors.
JEP 515 moves method-execution profiles from the training run into the AOT cache; it does not stop production profiling. Therefore “every production request follows the training path” is the wrong model.
2. Design the training run
Cover real routes, tenant sizes, serialization formats, cache hits and misses, exception paths, and common configuration. A health-only load test biases the profile toward the wrong hot methods. After training, compare request distributions with a recent production window and record the training-input version in the cache manifest.
If traffic is strongly seasonal, build separate caches for materially different shapes instead of applying a low-traffic profile to a peak-traffic release.
3. Choose one-step or two-step creation
JDK 25 supports -XX:AOTCacheOutput=app.aot for the common workflow of training and creating a cache in one launch. In a constrained environment, use two explicit steps: record during training and create the cache on a machine with more resources. JEP 514 notes that the cache-creation sub-invocation in the one-step workflow uses a Java heap the same size as the training run; two 4 GB heap settings can therefore require close to 8 GB at the peak.
java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App
java -XX:AOTCache=app.aot -cp app.jar com.example.App4. Treat the cache as a bounded build artifact
Include the exact JDK version, operating system, CPU architecture, class-path or image digest, launch flags, and training-data version in the cache key. Validate those fields before startup; any mismatch falls back to an uncached launch. Do not reuse a cache across CPU instruction sets or incompatible bytecode assumptions.
5. Validate gains and failures with a canary
Send a small set of instances the profile cache and compare them with uncached instances in the same time window. Measure time to steady throughput, not only process readiness. A faster first request with worse p99, CPU, or RSS means the target was chosen poorly. Because production keeps profiling online, watch for frequent deoptimization; it indicates that production behavior differs from training.
6. Define rollback and refresh rules
The cache must be independently removable. Keep an uncached startup path in the image and let the release controller select -XX:AOTCache; stop the canary and remove the flag when error rate, p99, or RSS crosses a threshold. Retrain after a JDK patch, dependency, route, or critical configuration change. An old cache is not a permanent asset.
High-quality sample answer
I would evaluate it as a version-bounded performance build artifact. First I would pin JDK 25, the image, CPU, and heap settings, then compare no cache, class-loading cache, and an AOT cache with method profiles. The training workload must cover real hot paths and exception paths, with its input version recorded. In the canary I would measure first request, time to steady throughput, p95/p99, CPU, RSS, deoptimization, and errors. JEP 515 makes historical observations available to the JIT earlier; it does not promise fixed behavior because production keeps profiling online. I would bind the cache to the JDK, architecture, class path, and training version, and fall back on mismatch. If the gain exists only at cold start or production causes deoptimization, p99, or memory regression, I would disable the cache, keep the uncached image, and retrain.
Common mistakes
- Mistake → calling an AOT profile full native compilation → JEP 515 caches method-execution profiles while the JIT still compiles in production; fix: distinguish a profile cache, a class-loading cache, and possible future AOT code.
- Mistake → training only a healthy path → production exceptions, tenants, and long-tail requests shift behavior; fix: cover important boundaries according to traffic distribution and record the training version.
- Mistake → comparing only process-ready time → ready sooner does not mean steady throughput arrives sooner; fix: measure first request, steady latency, CPU, RSS, and deoptimization.
- Mistake → reusing a cache across JDKs or CPUs → class paths, instruction sets, and runtime assumptions may differ; fix: put those fields in the manifest and validate them strictly.
Follow-up questions and responses
What if training data differs substantially from production traffic?
Compare route, tenant, response-code, and serialization distributions first. If the difference exceeds a defined threshold, stop the cache rollout, add training samples, or build separate caches for distinct traffic shapes. Online production profiling can correct the profile, but it cannot replace basic training coverage.
What if one-step cache creation OOMs in CI?
Use the explicit two-step workflow, train in an environment close to production, create the cache on a larger machine, and verify JDK, class path, and launch flags in both stages. You can lower the heap or split training, but you must remeasure profile coverage and build time.
Should you continue when the canary p99 worsens even though startup improves?
Stop expanding the canary. Check CPU, RSS, deoptimization, GC, and request-distribution changes; if the p99 regression is unexplained or exceeds the service threshold, roll back to the uncached version. Resume only after a reproducible cause is fixed with a new cache and a fresh controlled comparison.