Trace, in order, what a Java Virtual Machine does from the moment running code first references a type until that type's methods are executing as JIT-compiled native code — and name which JVM subsystem owns each step.
answer
- Load → verify → prepare → resolve → init → interpret → profile → compile
- First five: class loader subsystem. Last three: execution engine
- Data areas are storage, not steps
- Once per type (1-5) vs per method, repeatedly (6-8)
- Resolution may be lazy; compilation may never happen
basics
~20 sOrder: load, verify, prepare, resolve, initialize, interpret, profile, compile. The class loader subsystem owns the first five (loading through running the static initializer); the execution engine owns the last three (interpreting bytecode, gathering profile counters, and JIT-compiling hot methods into native code).
solid answer
~60 sThe trigger is first *active use* — a `new`, a static call, a static field read, reflection. **Class loader subsystem** then does five steps in this order: 1. **Loading** — a loader is asked for the type by name, delegates to its parent, finds the binary form, and the VM defines the runtime type in the class-metadata area plus a `Class` object. 2. **Verification** — proves the bytecode is well-formed and type-safe. 3. **Preparation** — allocates static fields at *default* values (0/null), not their initializers. 4. **Resolution** — turns symbolic constant-pool references into direct ones; permitted to be lazy. 5. **Initialization** — runs `<clinit>` once; superclasses first. **Execution engine** takes over from there: 6. **Interpretation** — every method starts interpreted, so execution begins immediately. 7. **Profiling** — invocation and loop back-edge counters plus type/branch profiles accumulate while interpreting. 8. **JIT compilation** — hot methods compile on a background thread into the code cache; later calls jump into native code. Steps 1–5 happen once per type; 6–8 repeat per method and overlap in time.
code
text · 9 lines$ java -Xlog:class+load=info -XX:+PrintCompilation -cp app App
[0.412s][info][class,load] com.example.OrderService source: file:/app/app.jar
[0.413s][info][class,load] com.example.Order source: file:/app/app.jar
118 34 3 com.example.Order::total (24 bytes) <- tier 3, profiled
902 61 4 com.example.Order::total (24 bytes) <- tier 4, fully optimized
1204 61 4 com.example.Order::total (24 bytes) made not entrant <- deoptimized
# Note: OrderService loads but never appears in the compilation log --
# a cold method stays interpreted for the life of the process.go deeper
Recall the phase names in the right order and be able to say the class loader handles getting the class ready and the execution engine handles running it. Knowing that execution starts interpreted and speeds up later is enough.
Give all eight steps in order with correct subsystem attribution, and be precise that preparation sets defaults while initialization runs the real initializers. Mention that classes load lazily on first use, not at startup.
Add what is optional or reordered — lazy resolution, methods that never compile, deoptimization moving backwards from compiled to interpreted — and map real symptoms (VerifyError, NoClassDefFoundError mid-request, a slow warm-up window) onto the phase that produced them.
Frame the split as a design decision: laziness and interpret-first buy startup latency and let the compiler use real profiles rather than static guesses, at the cost of a warm-up window and non-deterministic peak timing. Discuss when that tradeoff argues for AOT/CDS-style measures versus accepting warm-up, and what it implies for capacity planning and canary duration.
## Why the ordering is the answer A JVM has three top-level subsystems: the **class loader subsystem**, the **runtime data areas**, and the **execution engine**. Naming them is easy; placing them *in time* is what this question tests. Almost every confusing JVM behaviour — a static block running later than you expected, a missing dependency that only blows up on the line that uses it, code that gets several times faster after a few seconds of load — is explained by knowing which step runs when and who runs it. The full sequence is: **load → verify → prepare → resolve → initialize → interpret → profile → compile.** The first five belong to the class loader subsystem. The last three belong to the execution engine. The runtime data areas are not steps at all — they are the storage every step writes into (the metadata area receives the defined type and its runtime constant pool, the heap receives the `Class` object and later the instances, the code cache receives compiled native code). ## The trigger Nothing in the list happens at JVM startup for your classes. A type is processed on first *active use*: instantiating it, invoking one of its static methods, reading or writing a non-constant static field, reflecting on it in a way that requires initialization, or being the superclass of a type that is itself initializing. This laziness is why the sequence is per-type and interleaved with normal execution rather than a startup phase. ## Steps 1–5: the class loader subsystem **1. Loading.** A class loader is asked for the type by fully-qualified name, delegates upward to its parent first, and if no ancestor supplies it, locates the binary form itself. It then asks the VM to *define* the type, which builds the internal type structures and the runtime constant pool in the class-metadata area and creates the corresponding `Class` object. A type's runtime identity is the pair (name, defining loader), not the name alone. **2. Verification.** A one-time static proof that the bytecode is well-formed and type-safe. Detail lives with the linking material; for this trace, what matters is that it runs *after* loading and *before* any of the class's code can execute. **3. Preparation.** Static fields get storage and their *default* values — 0, false, null. Their declared initializers have not run yet. **4. Resolution.** Symbolic constant-pool references (class names, field and method descriptors) become direct references to real runtime structures. The specification explicitly allows this to be lazy, and HotSpot resolves on first use — which is the one place the strict ordering bends. **5. Initialization.** The JVM runs the synthetic `<clinit>` method — static blocks and static-field initializers — exactly once, superclasses first. This is where a `static int limit = 100;` finally becomes 100. Steps 2–5 are collectively "linking and initialization"; each has its own mechanics worth studying separately. For the trace, hold on to two facts: they run strictly in that order, and they all complete before the type's ordinary methods can run. ## Steps 6–8: the execution engine **6. Interpretation.** With the type initialized, its methods can execute. Every method starts *interpreted*: the engine decodes and executes one bytecode at a time, pushing a frame per invocation and advancing a program counter. Nothing is compiled up front, which is exactly why a JVM starts responding immediately instead of pausing to compile. **7. Profiling.** While interpreting, the engine counts method invocations and loop back-edges, and records a behavioural profile — which receiver types actually appear at a call site, which branches are taken, whether a null was ever observed. Profiling is not a separate pass; it is bookkeeping done *during* step 6. **8. JIT compilation.** When the counters cross a threshold, the method is queued for compilation on a background compiler thread while execution continues in the interpreter. The finished native code is installed in the code cache and the entry point is patched, so subsequent calls run compiled. A method stuck in a long-running loop can be switched over mid-execution by on-stack replacement. Because the compiler holds the profile, it can inline through virtual calls and speculate; if a speculation is later violated, the frame deoptimizes back to the interpreter and the method is recompiled. ## Which parts are optional or reordered - Steps 1–5 happen **once per type**; steps 6–8 are **per method**, repeatedly, over the process's lifetime. - Resolution (4) may be deferred past initialization to first use of the particular reference. - Compilation (8) may never happen — a method called twice stays interpreted forever, and that is normal, not a failure. - Deoptimization moves a method *backwards* from 8 to 6; the compiled/interpreted boundary is not one-way. ## Reading the trace backwards to debug Given a symptom, the ordering tells you the phase. A malformed class file fails at verification, before any of its code runs. A `NoClassDefFoundError` deep in a request path points at lazy resolution or a failed earlier initialization. A static field seen as 0 or null points at code observing the type between preparation and initialization. A workload that is slow for the first seconds and then fast points at steps 6–8 doing their job. Same class file, same phases, whichever language emitted the bytecode.
- For each of these steps — verification, initialization, profiling, and JIT compilation — say which JVM subsystem performs it.Verification and initialization belong to the class loader subsystem: they are part of linking and initializing a type, and both finish before that type's ordinary methods can run. Profiling and JIT compilation belong to the execution engine: profiling is counter and type bookkeeping done while the interpreter runs the bytecode, and compilation is a background activity of the same engine that installs native code in the code cache. The runtime data areas own none of the steps — they are the storage the other two subsystems write into.
- Which steps in the sequence happen exactly once, and which repeat? Can anything move backwards?Loading through initialization happen once per (type, defining loader) pair — a type is never verified or initialized twice for the same loader. Interpretation, profiling and compilation are per method and ongoing: a method may be interpreted thousands of times, compiled, then recompiled at a higher tier. It can also move backwards — when a JIT speculation based on the profile turns out to be wrong, the frame deoptimizes from compiled code back to the interpreter, and the method is later recompiled with the corrected profile.
- Where does the strict ordering actually bend, and where does it never bend?Resolution is the flexible one: the specification permits it to be lazy, and HotSpot typically resolves a constant-pool entry on first use, which can be after initialization has completed. Compilation is optional entirely — a method that never gets hot stays interpreted for the process's life. What never bends is that loading, verification and preparation all precede initialization, and that initialization precedes any execution of the type's ordinary methods; superclasses also always initialize before subclasses.
Steps 1–5 are a customs checkpoint a shipment clears exactly once — inspected, stamped, unpacked. Steps 6–8 are the warehouse floor: the goods start moving by hand immediately, and only the routes that turn out to be busy get a conveyor belt built for them.
saying these in an interview costs you the question
- Saying the JIT compiles methods up front at class load, or that classes are 'compiled to native code when loaded' — every method starts interpreted, and compilation is triggered later by profiling counters.
- Placing initialization before preparation, or claiming static fields hold their declared values as soon as the class is loaded.
- Claiming all classes are loaded at JVM startup rather than lazily on first active use.
- Assigning JIT compilation or profiling to the class loader subsystem, or treating the heap/stacks as a 'step' in the sequence instead of storage the steps write into.
- Asserting every method eventually gets compiled, and treating a method that stays interpreted as a bug or a misconfiguration.