When running code references a class for the first time, the JVM has to load it. Concretely, what happens during that loading step — where do the class's bytes come from, and what does the JVM end up with in memory?
answer
- Load → link → initialize; loading is step one
- Loader finds bytes; JVM makes the type
- 0xCAFEBABE, constant pool, superclass loaded first
- Metadata in Metaspace + Class mirror on heap
- Lazy, per class, no <clinit> yet
basics
~20 sA class loader is asked for a binary name and produces the class's bytes (usually a .class file on the classpath, but any source works). The JVM parses that byte stream, loads the superclass and superinterfaces first, and creates a runtime type: internal class metadata plus a java.lang.Class object on the heap.
solid answer
~60 sLoading is the first of the three class-lifecycle activities (loading, linking, initialization). The JVM hands a binary name such as `com.acme.Order` to a class loader; the loader's job is to *find bytes*. Normally those bytes are a `.class` file inside the runtime image, a JAR, or a classpath directory, but the JVM only requires a byte sequence in the classfile format — it can equally come from a network socket, a database blob, or a generator running in memory. The JVM then parses the stream (magic `0xCAFEBABE`, version, constant pool, fields, methods, attributes), checks basic structural well-formedness, recursively loads the direct superclass and superinterfaces, and derives the runtime type: metadata in the class-metadata area (Metaspace in HotSpot) holding the runtime constant pool, field and method descriptors, and bytecode, plus a `java.lang.Class` *mirror* object on the heap that reflection and `.class` literals hand you. Loading is per class and normally lazy. It does **not** verify all bytecode, allocate statics, resolve symbolic references, or run static initializers — those are later stages.
code
text · 5 lines$ java -verbose:class -cp app.jar com.acme.Main
[class,load] java.lang.Object source: shared objects file
[class,load] com.acme.Main source: file:/home/dev/app.jar
[class,load] com.acme.Order source: file:/home/dev/app.jar
[class,load] com.acme.Order$$Proxy12 source: __JVM_DefineClass__go deeper
Be able to say: loader finds the bytes, JVM parses them and creates the runtime type plus a Class object, and this happens lazily per class.
Add the ordering details — superclass and superinterfaces load first, structural checks belong here, and initialization is a separate later stage with its own trigger rules.
Connect it to diagnosis: -verbose:class to see which source a class came from, version errors surfacing at load time, and the fact that byte provenance is entirely under the loader's control.
Frame it as an extension point: because loading only requires a byte array in classfile format, the platform supports agents, isolation, dynamic code generation and packaging strategies without any language change.
## Where loading sits in the class lifecycle Bringing a type into a running JVM is specified as three activities: **loading**, **linking** (verification, preparation, resolution) and **initialization**. Loading is the first and the narrowest: it starts from a *binary name* (`com.acme.Order`, or `com/acme/Order` in internal form) and ends with a runtime type the JVM can talk about. Everything about proving the bytecode is legal, creating storage for static fields, replacing symbolic constant-pool entries with direct references, and executing static initializers happens after loading, in the later stages. ## Step 1 — obtain the bytes A class loader is asked for the name, and it is the *loader's* job, not the JVM's, to produce bytes. The built-in loaders search well-known places: the bootstrap loader serves the core modules of the runtime image (`java.base` and friends), the platform loader serves the remaining JDK modules, and the application (system) loader serves the class path or module path of your program. What the JVM requires is only that the bytes conform to the **classfile format**: a 4-byte magic number `0xCAFEBABE`, minor and major version numbers, the constant pool, access flags, this-class and super-class indices, interfaces, fields, methods, and attributes. Nothing in that contract mentions a file system. Bytes may just as legitimately arrive over HTTP, be read from a database column, be decrypted on the fly, or be synthesised in memory by a bytecode generator — which is exactly how dynamic proxies, mocking frameworks and persistence enhancers work. ## Step 2 — parse and derive the runtime type The JVM parses the byte stream and performs the cheap structural checks that loading owns: is this a classfile at all, is the major version one this JVM supports, does the name inside match the name that was requested? Failures here surface as `ClassFormatError`, `UnsupportedClassVersionError` or `NoClassDefFoundError` with a "wrong name" message. Loading is also recursive. Before the JVM can create the runtime type for `C`, it must load `C`'s direct superclass and all its direct superinterfaces, because the type's shape depends on them — field layout continues from the superclass, and method tables are built on top of the inherited ones. A cycle in that graph (a class that is its own ancestor) is detected here and reported as `ClassCircularityError`. ## Step 3 — what actually exists in memory afterwards Loading produces two linked artifacts: 1. **Class metadata**, kept in the JVM's class-metadata area — Metaspace in HotSpot, which is native memory outside the Java heap. This holds the runtime constant pool, the field and method descriptors, the method bytecode, the inheritance links, annotations, and the dispatch tables. 2. **A `java.lang.Class` object on the heap** — the *mirror*. This is the Java-visible handle: what a `.class` literal, `getClass()`, `Class.forName` or reflection returns. The mirror and the metadata point at each other. Only after both exist can anything else — verification, static-field preparation, resolution, `<clinit>` — proceed. ## Loading is per class and normally lazy The JVM does not read your whole application at startup. A class is typically loaded when something first needs it: a constant-pool entry naming it is resolved, `Class.forName` is called, an instance is created, or reflection asks for it. Each class is loaded independently; a class you never touch on a given run may never be read at all. That laziness is why an application can start with a huge classpath and still boot quickly, and why a missing dependency can lie dormant for hours before an unlucky code path trips over it. ## What loading is explicitly *not* - It is **not** full bytecode verification. Structural sanity is checked here; the dataflow verification that proves stack and type safety is part of linking. - It does **not** allocate or assign static fields, and it does **not** run static initializers or field initializers. A class can be loaded and never initialized. - It does **not** resolve the symbolic references in the constant pool to other classes and members — that happens on demand later. ## Why this matters in practice Understanding that loading is just "get bytes, make a type" explains a lot of everyday behaviour: why a JAR built for a newer Java release fails with `UnsupportedClassVersionError` the moment it is read rather than at compile time; why frameworks can materialise classes that no build ever produced; why `-verbose:class` is a good first diagnostic when you suspect the wrong copy of a class is in play, since it prints each loaded class *and the source it came from*.
- If a class is loaded but never initialized, what can you still do with it?You can hold its `java.lang.Class` mirror, ask for its name, superclass, declared fields and methods, and use it as a type in signatures — all of that only needs the metadata that loading produced. What you cannot do is observe any effect of its static initializer, because reading or writing a non-constant static field, calling a static method, or instantiating it are exactly the actions that trigger initialization.
- You get `UnsupportedClassVersionError` at runtime. Which stage produced it and what does the message tell you?It comes from loading, when the JVM parses the classfile header and finds a major version newer than it supports. The message reports both versions (for example "class file version 65.0, this JVM recognizes up to 61.0"), so it identifies a library or module compiled for a newer Java release than the JVM running it. The fix is to run a newer JVM or rebuild the artifact with a lower `--release` target.
- Does loading a class also load every class it mentions?No. It loads the direct superclass and direct superinterfaces, because the runtime type cannot be derived without them, but the classes named in method signatures, field types and method bodies are only symbolic constant-pool entries at this point. They get loaded when those references are actually resolved, which for most of them is on first use.
Loading is like a librarian fetching a book and cataloguing it: someone finds the physical copy (from a shelf, another branch, or a photocopy), then the library creates a catalogue record for it. Reading the book, checking it for errors, and acting on its contents all come later.
saying these in an interview costs you the question
- Saying the JVM loads all classes on the classpath at startup
- Claiming loading runs static initializers or assigns static fields
- Insisting class bytes must be a .class file on disk
- Describing full bytecode verification as part of loading
- Conflating the java.lang.Class mirror with the class's metadata as if there were only one structure