skip to content

HotSpot replaced the permanent generation with metaspace in Java 8. What concretely changed, and which data moved out of the permanent generation before that, in Java 7?

level: middleimportance: must knowfreq 56%

answer

  1. PermGen = fixed heap region, MaxPermSize
  2. Java 7: interned strings + statics to the heap
  3. Java 8: metadata to native metaspace
  4. MetaspaceSize = initial high-water mark, not a floor
  5. PermGen OOM replaced by OOM: Metaspace

basics

~20 s

Java 8 removed the fixed-size permanent generation inside the heap and moved class metadata to native memory (metaspace), which grows on demand. Earlier, Java 7 had already moved the interned-string pool and static field values out of PermGen onto the normal heap.

solid answer

~50 s

The permanent generation was a heap region with its own fixed maximum (`-XX:MaxPermSize`) holding class metadata plus, historically, interned strings and static field storage. It was removed in JDK 8. The migration happened in two steps. **Java 7** moved the **interned-string pool** and static field storage out of PermGen onto the regular heap: interned strings became ordinary heap objects reachable through a native lookup table, and statics moved into the `java.lang.Class` mirror. **Java 8** removed PermGen entirely and relocated the remaining class metadata to **metaspace**, a native-memory region committed from the OS on demand. Practical consequences: `-XX:PermSize`/`-XX:MaxPermSize` no longer exist (they are ignored with a warning), metadata is bounded only if you set `-XX:MaxMetaspaceSize`, interned strings are now collected by the ordinary collector like any other object, and metadata is released per class loader rather than by a heap collector managing a fixed pool.

go deeper

for a junior

Know that PermGen was removed in Java 8 and metadata now lives in native metaspace that grows on demand.

for a middle

Give both steps in order, name the flags that disappeared and the flag that now caps metaspace, and note interned strings are heap objects since Java 7.

for a senior

Add the high-water-mark semantics of MetaspaceSize, the loader-granularity reclamation, and why the default-unbounded behaviour needs an explicit cap in containers.

for a principal

Explain the design rationale: separating a class-count-driven, non-moving, loader-scoped budget from an allocation-rate-driven, moving, object-scoped one, and the monitoring migration that change forces.

## What the permanent generation was Until JDK 7 inclusive, HotSpot kept the runtime description of loaded classes in a heap region called the **permanent generation**. "Permanent" reflected the original assumption that classes load once and stay forever. It was sized at startup with `-XX:PermSize` and `-XX:MaxPermSize`, collected only during full collections, and it held three quite different kinds of data: 1. class metadata (type structures, method bytecode, runtime constant pools, dispatch tables); 2. the **interned-string pool** contents; 3. **static field storage** for each class. ## Step one: Java 7 moves strings and statics to the heap JDK 7 relocated the interned-string pool and static fields onto the ordinary Java heap. Two mechanics matter. - The **string table** is a native hash table, but from JDK 7 on it stores *references* to `String` objects that live on the heap. `String.intern()` therefore produces a normal heap object. Since JDK 11 that table is a resizable concurrent hash table, so `-XX:StringTableSize` sets only the initial bucket count and rarely needs tuning. - **Static field values** moved into the `java.lang.Class` mirror object, itself a heap object. Why it mattered: in the PermGen era, heavy interning (parsers, XML tooling, code that interned user input) could exhaust `MaxPermSize` while the object heap was nearly empty, and those strings were only collectable during a full collection of a region tuned for permanence. Once they were plain heap objects, an unreferenced interned string became garbage like anything else, collected by the ordinary young/old machinery. ## Step two: Java 8 removes the permanent generation JDK 8 deleted PermGen and moved the remaining class metadata into **metaspace**, native memory outside the Java heap. The changes a candidate should be able to list: - `-XX:PermSize` and `-XX:MaxPermSize` no longer exist. Passing them produces a warning and they are ignored; `java.lang.OutOfMemoryError: PermGen space` cannot occur. - Metadata is now **committed from the OS on demand** and is unbounded by default. You opt into a cap with `-XX:MaxMetaspaceSize`; exceeding it raises `OutOfMemoryError: Metaspace` instead of the old PermGen error. - `-XX:MetaspaceSize` is *not* a minimum allocation. It is the **initial high-water mark**: the first time committed metaspace crosses it, the JVM runs a collection to attempt class unloading, then adjusts the mark up or down according to how much was reclaimed. - Metadata is allocated from per-class-loader arenas and freed wholesale when a loader is unloaded, so metaspace reclamation is tied to class-loader lifecycles rather than to a compacting heap collector. - The memory-pool beans exposed by the runtime changed names accordingly: monitoring dashboards that watched "PS Perm Gen" had to be repointed at "Metaspace" and "Compressed Class Space". ## Why native memory was the right target Three independent pressures pushed the same way. Metadata volume scales with **class count**, not with allocation rate, so pinning it inside the object heap forced an artificial split of one budget. Application servers and frameworks that redeploy modules or generate proxies routinely blew a fixed ceiling that the machine had ample memory to satisfy. And metadata is unlike Java objects: it is never relocated, and it becomes garbage only in loader-sized units, which is a poor fit for a moving collector. ## What did *not* change The removal was a relocation, not a relaxation of class-unloading rules. Metadata still becomes reclaimable only when its class loader becomes unreachable, so a loader kept alive by a stray reference retains all of its metadata exactly as it did in the PermGen era. The failure mode moved from a bounded region with an obvious error to a native region that, by default, grows until something worse happens. That is precisely why setting an explicit maximum is standard practice in memory-limited containers. ## Answering it crisply Say: PermGen was a fixed heap region holding metadata, interned strings and statics. Java 7 moved strings and statics to the heap. Java 8 deleted the region and moved metadata to native metaspace, which grows on demand, is capped only via `-XX:MaxMetaspaceSize`, and is reclaimed per class loader. Then name one operational consequence: process memory now exceeds `-Xmx` by the metadata footprint.

  • What does -XX:MetaspaceSize actually do, given that it is not a minimum reservation?
    It sets the initial high-water mark for committed metaspace. When usage first crosses it, the JVM triggers a collection to attempt class unloading, then raises or lowers the mark based on how much was freed, guided by the free-ratio flags. Setting it near the application's steady-state footprint avoids a burst of early collections during warm-up.
  • After Java 7, can interning many strings still exhaust a memory region?
    Yes, but it is now ordinary heap pressure rather than a separate PermGen ceiling, so an interned string that becomes unreferenced is collected normally. The remaining cost is that the native string table itself grows and lookups take time; heavy interning of unbounded input is still a design smell, but it no longer fails against MaxPermSize.

saying these in an interview costs you the question

  • Saying Java 8 'made PermGen bigger' or 'renamed PermGen to metaspace' with no change in location
  • Claiming interned strings moved to metaspace in Java 8 (they moved to the heap in Java 7)
  • Treating -XX:MetaspaceSize as a guaranteed minimum or as the maximum
  • Believing the removal of PermGen made class-unloading rules more permissive
  • Still recommending -XX:MaxPermSize for modern JVMs

context