skip to content

Linking: Verify, Prepare, Resolve

The three linking sub-phases: verification for bytecode and type safety, preparation that gives static fields their default zero values, and resolution turning symbolic constant-pool references into direct ones. Interviewers use it to separate defaults from your initializers and to explain where VerifyError and NoSuchMethodError actually come from.

on this pageshow

questions

5

After the JVM has read a class file into the runtime, linking happens in three sub-phases before any of the class's own initializer code runs. Name them and describe what each one is responsible for.

level: middleimportance: must knowfreq 48%

answer

  1. Verify → Prepare → Resolve, then initialize
  2. Verify: stack discipline, type safety, StackMapTable
  3. Prepare: statics allocated, zero defaults, ConstantValue
  4. Resolve: symbolic → direct reference
  5. Spec allows lazy; HotSpot is lazy

basics

~20 s

Verification checks that the bytecode is structurally and type-safe and that the operand stack is disciplined. Preparation allocates the static fields and sets them to default zero values, not to their programmed initializers. Resolution replaces symbolic references in the constant pool with direct references, and may be done lazily.

solid answer

~50 s

Linking sits between loading and initialization and has three sub-phases defined by the JVM specification. **Verification** proves the class file is well-formed and the bytecode is type-safe: operand-stack depth and types agree at every merge point, jumps land on real instructions, locals are typed consistently, access rules hold, and an object is initialized before use. Malformed bytecode is rejected with a `VerifyError` rather than executed. **Preparation** allocates storage for the class's static fields and writes default values into them — zero, `false`, `null` — explicitly *not* the values written in the source, which are assigned later during initialization. Fields that are compile-time constants carry a `ConstantValue` attribute and get their value here. **Resolution** turns the constant pool's symbolic references — class names, name-and-type pairs — into direct references such as a field offset or a method entry. The specification permits eager or lazy resolution; HotSpot resolves lazily, on first execution of the referencing instruction.

go deeper

for a junior

Recall the three names and one sentence each: check the bytecode, allocate statics with default values, turn names into real references.

for a middle

Be precise that preparation writes zero defaults rather than the source initializers, and that resolution may be lazy.

for a senior

Connect each phase to an observable symptom — a VerifyError from a bytecode agent, a static field seen as null, a NoSuchMethodError that appears only when a rare path runs.

for a principal

Frame linking as the boundary that makes independently compiled artifacts safe to combine at run time, and reason about the cost and failure timing that lazy resolution shifts into production.

## Where linking sits A class goes through loading (reading the bytes and creating the runtime representation), then **linking**, then initialization (running static initializers). Linking is the phase that makes the class usable by the execution engine, and the specification splits it into verification, preparation and resolution — in that order, though implementations may interleave them with loading and may delay resolution arbitrarily. ## Verification: making untrusted bytecode safe The JVM executes bytecode from anywhere — compiled by any tool, generated at run time, or downloaded. Nothing about the source language can be assumed. Verification is the JVM's proof that the code cannot break the machine's invariants: it cannot pop from an empty operand stack, treat an integer as a reference, jump into the middle of an instruction, write a `String` into an `int` local, access a private member of another class, or use an object before its constructor has run. Modern class files carry a `StackMapTable` attribute, produced by the compiler, which states the types present at each branch target. The verifier then only has to *check* the claimed types rather than infer them by data-flow analysis, which is why verification is fast. If the check fails, the class is rejected with a `VerifyError`; execution of unsafe code never begins. ## Preparation: storage and defaults Preparation allocates memory for the class's static fields and gives each one its default value for its type: `0`, `0L`, `0.0`, `'\0'`, `false`, `null`. This matters because it defines what a static field holds *before* initialization runs — and a class can be observed in that state, for example by another class touched during a circular initialization. One exception: a static field that is a compile-time constant carries a `ConstantValue` attribute in the class file, and preparation assigns that value directly. Instance fields are not involved at all here; their storage belongs to objects and is zeroed at allocation. ## Resolution: symbols become addresses The class file never contains addresses. Every reference to another class, field or method is *symbolic*: a name in the constant pool, plus a descriptor. Resolution is the act of turning `Methodref -> java/util/List.add:(Ljava/lang/Object;)Z` into something the interpreter or compiled code can use directly — a vtable/itable index, a field offset, or a pointer to a method structure. Resolving a reference means locating the target class through the appropriate loader, applying the specification's field- or method-lookup algorithm (search the class, then superclasses, then superinterfaces, choosing the maximally specific method), and checking access. If the member is absent or inaccessible, resolution fails. The specification deliberately permits two strategies. *Eager* resolution links everything at link time — simple, but it forces loading of every referenced class. *Lazy* resolution defers each reference until the instruction that uses it first executes. HotSpot is lazy, which is why a class whose method references a missing type loads and runs happily until the branch that calls into that type is actually taken. ## Why the three-phase split is worth knowing It explains behaviour candidates otherwise find mysterious: static fields visibly holding zeros before initializers run; failures that appear only when a rare code path executes; classes that load fine and then reject a bytecode-rewriting agent's output. Each of those maps to exactly one sub-phase.

  • Is the order of the three sub-phases strictly fixed?
    Verification and preparation precede any use of the class, and in that sense are ordered. Resolution is deliberately unconstrained: the specification allows an implementation to resolve every constant-pool entry at link time or to defer each one until first use. HotSpot defers, so resolution routinely happens long after the class has been initialized and is running.
  • Where does initialization fit relative to linking, and what distinguishes it from preparation?
    Initialization follows linking and runs the class's static initializer code, assigning the values written in the source. Preparation only allocates the static fields and stamps default zero values into them. A static field observed between the two phases holds its default, not the programmed value.

saying these in an interview costs you the question

  • Merging preparation and initialization — claiming static fields get their source values during preparation
  • Believing resolution must complete before the class can be used
  • Thinking verification checks program logic or business correctness rather than type and structural safety
  • Placing verification after initialization

context

open as a page

In the JVM, when exactly does a Java `static int counter = 5;` field actually hold the value 5 — and how is `static final int LIMIT = 100;` treated differently by the class file and the runtime?

level: middleimportance: should knowfreq 40%

basics

~20 s

During preparation the field is allocated and set to 0; the value 5 is only written when the class is initialized and its static initializer runs. A compile-time constant like static final int LIMIT = 100 carries a ConstantValue attribute and is set during preparation — and javac also copies the literal into every class that reads it.

open as a page

A Java application starts and runs for minutes, then throws NoSuchMethodError deep in a rarely used branch. Explain, in terms of how the JVM turns constant-pool symbolic references into direct references, why the failure surfaced only then.

level: seniorimportance: should knowfreq 42%

basics

~20 s

Class files reference members symbolically — by name and descriptor in the constant pool. HotSpot resolves each reference lazily, on first execution of the instruction that uses it. The rare branch's method reference was never resolved before, so the mismatch between the compiled-against and loaded classes only surfaced when the branch finally ran.

open as a page

What does the JVM's bytecode verifier actually prove about a class file, and why does the JVM verify at all when the bytecode was produced by a Java compiler?

level: seniorimportance: should knowfreq 38%

basics

~20 s

The verifier proves structural and type safety: operand-stack depth and types agree on every path, jumps target real instructions, locals are used with consistent types, access rules hold, and objects are initialized before use. It verifies because the JVM must assume bytecode came from anywhere, not from javac.

open as a page

A JVM service generates a large number of classes at run time and its start-up time is dominated by class loading. How does the bytecode verification phase factor into that cost, and what levers would you consider?

level: principalimportance: nice to knowfreq 22%

basics

~20 s

Verification is per-class work proportional to bytecode size and branch structure, and it is a significant part of class-load cost for application classes — bootstrap classes are trusted and skipped by default. Levers: generate fewer and smaller classes, reuse them, and use class-data archives that store already-linked classes, rather than disabling verification, which modern JDKs ignore.

open as a page