Does the JVM guarantee that a class is loaded only at the moment it is first used? What latitude does the specification give an implementation about *when* loading happens, and how is that latitude kept unobservable?
answer
- Loading time = unspecified; initialization time = specified
- Eager loading legal if errors are deferred to first use
- LinkageError reported at use point, and is sticky
- HotSpot lazy by default; CDS/AppCDS pre-maps metadata
- `source: shared objects file` in -verbose:class
basics
~20 sNo. An implementation may load classes eagerly, in bulk, or from a prebuilt archive. The only requirement is that any error from loading or linking is thrown at the point where the program would first actively use the class — so early loading must stay invisible. Initialization timing, by contrast, is strictly specified.
solid answer
~60 sThe specification deliberately does not fix when loading happens. A JVM is free to load a class early — speculatively, at startup, or by mapping a prebuilt archive — as long as it behaves *as if* the class were loaded at first use. The binding rule is about **errors**: any `LinkageError` that eager work discovers must be withheld and reported only when the program actually reaches a point where the class would be used. That is what makes the latitude unobservable. HotSpot loads lazily in practice, driven by constant-pool resolution, `Class.forName`, and reflection. But **Class Data Sharing** turns that on its head: the JVM memory-maps a prepared archive of class metadata at startup (`-Xshare`, AppCDS via `-XX:SharedArchiveFile`), so a large set of classes is effectively present before any code runs. `-verbose:class` shows those with `source: shared objects file`. Note the contrast with the next stage: *initialization* timing is precisely specified — the JVM must run `<clinit>` exactly at the specified triggering events, no earlier. So "loaded" says nothing about "initialized".
code
text · 4 lines$ java -Xshare:auto -XX:SharedArchiveFile=app.jsa -Xlog:class+load=info -jar app.jar
[class,load] java.util.HashMap source: shared objects file
[class,load] com.acme.Order source: shared objects file
[class,load] com.acme.Report source: file:/opt/app/app.jargo deeper
Know that classes are normally loaded on demand rather than all at startup, and that missing classes can therefore fail late.
State that loading timing is implementation-latitude while initialization timing is specified, and that errors must be deferred to the point of use.
Bring in CDS/AppCDS as legal eager loading, use -Xlog:class+load to read sources, and explain late NoClassDefFoundError as a code-path story rather than a classpath change.
Weigh the operational tradeoffs: archives and warm-up shift cost from steady state to startup, matter most for short-lived or rapidly scaled processes, and must be validated for archive/classpath drift.
## The specification is loose about loading and strict about initialization The JVM specification separates two questions that candidates often merge: 1. **When may a class be loaded?** — deliberately unconstrained. 2. **When must a class be initialized?** — precisely constrained, with an explicit list of triggering events. An implementation is allowed to load a class "early", including at JVM startup, including classes the program never ends up touching. What it may **not** do is let that choice change observable behaviour. ## The rule that makes eagerness safe: deferred errors The mechanism the specification uses is simple and worth being able to state: if loading (or subsequent linking) of a class fails, the resulting `LinkageError` — `NoClassDefFoundError`, `ClassFormatError`, `VerifyError`, `UnsupportedClassVersionError`, `ClassCircularityError` — must be reported at a point in the program where the class would have been used, not at the moment the implementation happened to touch it. So a JVM that pre-loads a hundred classes and discovers one is corrupt must *remember* the failure and throw it only when the program first actively uses that class. A program that never uses it must never see an error. This is the entire reason speculative loading is legal: from the program's point of view, nothing changed. The converse is also worth noting: the failure is **sticky**. Once a class has failed to load or link in a loader, subsequent attempts see the same failure rather than a fresh retry. ## What HotSpot actually does By default, HotSpot is lazy. A class is loaded when something needs it: - resolution of a symbolic reference from another class's constant pool (a `new`, a field access, a call, a type in a signature that must be checked), - an explicit `Class.forName(...)` or `ClassLoader.loadClass(...)`, - reflective or method-handle lookups, - loading of a subclass, which forces its supertypes. That laziness is why an application with a huge classpath can start quickly, and why a missing optional dependency can lie dormant until an unlucky code path finally resolves it — a classic "works for weeks, then `NoClassDefFoundError` at 3 a.m." story. ## Class Data Sharing: legal eagerness The most important production counterexample is **Class Data Sharing (CDS)**. A CDS archive contains class metadata in a form the JVM can memory-map directly at startup, skipping parse-and-derive work for those classes: - The default archive covers core JDK classes and is on by default. - **AppCDS** extends it to application classes: record a class list or dump a dynamic archive, then run with `-XX:SharedArchiveFile=...`. - Later JDKs added conveniences such as auto-creating the archive on first run and richer ahead-of-time caching work. CDS is exactly the latitude in action: metadata for classes exists before the program starts, yet the program cannot tell — initialization still happens on the specified triggers, and errors still surface at use. What CDS buys is startup time and, because the archive is mapped read-only, shared pages between JVMs on the same host. ## Consequences for how you write and reason about code - **Never treat "class loaded" as a side-effect trigger.** If you need something to happen at a defined moment, do not rely on the loader having pulled in the class. Initialization has specified triggers; loading does not. - **Startup diagnosis.** `-verbose:class` (or the `class+load` unified-logging tag) tells you both what got loaded and its source; classes shown as coming from a shared archive were not parsed from disk at all. Counting loaded classes is a legitimate startup-cost metric. - **Failure timing is not evidence of when work happened.** A `NoClassDefFoundError` appearing at a particular call site tells you the class was *needed* there; it does not tell you the JVM first tried to read it there. - **Warm-up strategies.** Some systems deliberately force loading early (touching classes on a warm-up path) so that the parse/verify/JIT cost is paid before traffic arrives. That is a legitimate operational choice, distinct from anything the spec requires. ## The boundary with initialization Because the two are so often conflated, be explicit in an interview: a class can be loaded and remain uninitialized indefinitely. Holding its `Class` mirror, naming it in a signature, or having its metadata mapped from a CDS archive are all non-events for initialization. Only the specified active-use events cause `<clinit>` to run, and the JVM must not run it earlier — that timing, unlike loading's, is guaranteed.
- If a JVM pre-loads a class whose bytes are corrupt, when may it report the error?Only at a point where the program would actively use that class. The implementation must record the failure and stay silent until then, so a program that never touches the class never observes an error. The failure is also sticky: a later attempt to use the class reports the same error rather than retrying the load.
- How does Class Data Sharing change startup, and what does it not change?It memory-maps prepared class metadata from an archive, so the JVM skips reading and parsing those classfiles, cutting startup time and letting several JVMs share read-only pages. It does not change semantics: initialization still runs only on the specified triggers, resolution is still performed as needed, and errors still surface at use.
- You see a `NoClassDefFoundError` deep into a long-running service. What does its timing tell you?It tells you the code path that first required the class ran at that moment — a rarely exercised branch, a lazily initialised feature, or a reflective lookup. It does not mean the JVM only just discovered a problem, and it does not mean the classpath changed. The usual cause is an optional or transitively missing dependency that nothing had needed before.
saying these in an interview costs you the question
- Asserting the spec guarantees loading happens exactly at first use
- Treating "loaded" and "initialized" as the same event with the same timing rules
- Claiming CDS changes initialization order or semantics
- Thinking a repeated attempt to load a previously failed class gets a fresh chance
- Concluding from the timing of a LinkageError that the JVM read the classfile at that instant