skip to content

Loading a Java class produces two related in-memory structures: the class metadata the JVM keeps in its class-metadata area (Metaspace in HotSpot) and the `java.lang.Class` instance on the heap. What lives in each, and how are they connected?

level: middleimportance: should knowfreq 38%

answer

  1. Metadata = native Metaspace; mirror = heap object
  2. Runtime constant pool, bytecode, vtables → metadata
  3. Class literals, reflection, statics → mirror
  4. Object header's class pointer → metadata
  5. Lifetime tied to the defining loader

basics

~20 s

Metadata lives in native memory (HotSpot Metaspace): runtime constant pool, field and method descriptors, method bytecode, inheritance links and dispatch tables. The heap holds a java.lang.Class mirror — the Java-visible handle used by reflection and .class literals, which in HotSpot also stores the class's static field values. The two reference each other.

solid answer

~60 s

Loading derives one logical type represented by two physical structures. **Class metadata** sits in the JVM's class-metadata area — in HotSpot that is *Metaspace*, native memory outside the Java heap since JDK 8 (it replaced PermGen). It holds everything the execution engine needs: the runtime constant pool, field and method descriptors, method bytecode, annotations, the superclass/interface links, and the virtual and interface dispatch tables. **The `java.lang.Class` mirror** is an ordinary heap object. It is what `Foo.class`, `getClass()` and `Class.forName` return, and it is the entry point for reflection. In HotSpot the mirror also carries the class's **static field values**, which is why statics are traced by the garbage collector like any other heap references. Each points at the other: the metadata holds a pointer to the mirror, and the mirror holds a pointer back to the metadata so reflective calls can read descriptors. Both are reachable from — and share the lifetime of — the defining class loader, so metadata is only reclaimed when the whole loader becomes unreachable.

code

text · 6 lines
text
# Heap size does not bound class metadata
-Xmx2g -XX:MaxMetaspaceSize=256m

# GC log line showing the two are accounted separately
[gc,metaspace] Metaspace: 41984K(43008K)->41984K(43008K)
               NonClass: 36864K(37632K)  Class: 5120K(5376K)

go deeper

for a junior

Know that .class and getClass() give you a Class object on the heap, and that the JVM separately keeps internal metadata for the type.

for a middle

Name what each structure holds, that Metaspace is native memory outside -Xmx, and that statics live in the mirror in modern HotSpot.

for a senior

Use the split to explain memory behaviour — Metaspace growth from class generation, statics as reachable roots, and per-loader chunk release.

for a principal

Reason about it as a capacity dimension of its own: class count and loader count drive native footprint independently of heap sizing, which matters for container limits and multi-tenant deployment models.

## One type, two structures When the JVM finishes loading a class it has *derived a runtime type*, and that type is physically represented twice: once as internal metadata the execution engine uses, and once as a Java object your code can hold. Neither is redundant — they serve different consumers. ## The class-metadata area (Metaspace in HotSpot) This is the JVM's own bookkeeping for a type, and in HotSpot it lives in **native memory**, not the Java heap. Historically it lived in a fixed-size heap region called PermGen; JDK 8 removed PermGen and introduced **Metaspace**, which grows on demand up to `-XX:MaxMetaspaceSize` (unbounded by default, bounded by the process's address space) and is allocated in chunks per class loader. What it holds: - **The runtime constant pool** — the classfile constant pool after loading, whose entries start as symbolic references ("a method named `size` with descriptor `()I` on the class named `java/util/List`") and get replaced with direct references as they are resolved. - **Field and method descriptors**, access flags, names, signatures, exception tables. - **Method bytecode**, plus per-method profiling and compilation state used by the interpreter and JIT. - **Inheritance links** to the superclass and superinterfaces. - **Dispatch structures** — the virtual method table and interface tables that make `invokevirtual` and `invokeinterface` fast. - **Annotation data** and other attributes. This structure is not directly addressable from Java code. You observe it only indirectly: through Metaspace metrics in GC logs or `jstat -gc`, through `-XX:MaxMetaspaceSize` tuning, and through the `java.lang.OutOfMemoryError: Metaspace` message when a process keeps defining classes. ## The `java.lang.Class` mirror The mirror is a normal heap object of type `java.lang.Class`. Every loaded class, every array type, and even every primitive type and `void` has exactly one. It is what you get from: - a class literal — `String.class` - `Object.getClass()` - `Class.forName("com.acme.Order")` - reflective navigation — `getSuperclass()`, `getInterfaces()`, `getComponentType()` The mirror is the API surface: `getDeclaredMethods()`, `getAnnotations()`, `getRecordComponents()` and friends read metadata through it and materialise `Method`, `Field` and `Annotation` objects on demand. A detail worth knowing for HotSpot specifically: since PermGen's removal, the **values of static fields are stored in the mirror object** on the heap. That has a pleasing consequence — a static reference field is an ordinary heap reference held by an ordinary heap object, traced by the collector like any other, with no special "root scanning of PermGen" needed. ## How they are wired together The relationship is bidirectional: - Metadata contains a pointer to its mirror, so runtime operations that must hand Java code a `Class` (`getClass()`, `ldc` of a class literal, reflection, exception construction) can produce it in one hop. - The mirror contains a pointer back to the metadata, so reflective queries can read descriptors and the object header's class pointer can be recovered. Every object instance on the heap carries a class pointer in its header referring to the metadata (compressed to a 32-bit class-pointer index when compressed class pointers are enabled) — that is how the JVM knows an object's type without touching the mirror at all on the fast path. ## Lifetime and the loader Both structures belong to the class's **defining loader**. The loader references its classes and the classes reference their loader (a mirror exposes `getClassLoader()`), so a class cannot outlive its loader and a loader cannot be collected while any of its classes are reachable. In HotSpot the metadata for all classes of one loader is allocated in that loader's own Metaspace chunks, so when the loader and all its classes become unreachable, the whole chunk set is released at once. Classes defined by the bootstrap loader are effectively permanent, because the bootstrap loader is never collected. ## Why an interviewer asks this The two-structure model explains several things at once: why class-heavy applications consume native memory that never shows up in a heap histogram of instances; why `-Xmx` does not bound class metadata; why static fields behave as GC roots for object graphs; and why reflection has a cost profile different from ordinary calls — it goes through the mirror and materialises API objects, rather than using the dispatch tables directly.

  • If static field values live in the `java.lang.Class` mirror on the heap, what does that imply for garbage collection?
    It means a static reference field is just a reference held by a live heap object, so the collector traces it exactly like any other field — no special treatment for a separate permanent region is required. It also means anything a static field points at stays reachable for as long as the class stays loaded, which is as long as its defining loader is alive.
  • Why can a process throw `OutOfMemoryError: Metaspace` while the Java heap is nearly empty?
    Metaspace is native memory holding class metadata, sized independently of `-Xmx` and bounded by `-XX:MaxMetaspaceSize` if set. A workload that keeps defining new classes — repeated deployments creating fresh loaders, or heavy runtime code generation — grows metadata without growing the object heap, so the two limits are hit independently.

saying these in an interview costs you the question

  • Saying class metadata lives on the Java heap and is bounded by -Xmx
  • Referring to PermGen as the current HotSpot structure
  • Thinking `java.lang.Class` is a special non-heap entity rather than an ordinary heap object
  • Believing an object's header points at its `java.lang.Class` mirror rather than at its metadata
  • Assuming metadata is freed per class as soon as the class is unused, rather than with its whole loader

context