Where is String Pool Stored (Which Memory Area)
The String Pool (or String Constant Pool) is a specialized JVM caching mechanism engineered to optimize memory consumption by storing and sharing unique immutable string instances.
🟢 Junior Level
The String Pool (or String Constant Pool) is a specialized JVM caching mechanism engineered to optimize memory consumption by storing and sharing unique immutable string instances.
The physical placement of the String Pool has evolved across major Java releases:
- Java 6 and earlier: The String Pool was located in PermGen (Permanent Generation) — a fixed-size memory area reserved for class metadata. Heavy string interning frequently resulted in fatal
java.lang.OutOfMemoryError: PermGen spacecrashes. - Starting from Java 7 (Java 7u6) through all modern versions (Java 8, 11, 17, 21+): The String Pool was relocated to and resides permanently within the main Java Heap.
The Essence in 30 Seconds
In modern Java, the String Pool is located in the Java Heap. It shares the exact same memory space as standard application domain objects and is constrained by the maximum heap limit (-Xmx).
The Garbage Collector (GC) automatically sweeps unreferenced pooled strings whenever they are no longer strongly referenced by application threads.
[!WARNING] Common Fallacy: Many engineers assume that when Java 8 removed PermGen, the String Pool moved to
Metaspace. This is incorrect:Metaspacestores class metadata off-heap, while the String Pool remains firmly in the Java Heap.
🟡 Middle Level
Architectural Separation: Heap Objects vs. Native Index
When examining the JVM at an architectural level, the “String Pool” consists of two distinct components:
- The String Objects (
java.lang.Stringandbyte[] valuearrays): Reside inside the Java Heap (typically promoted to the Old Generation if long-lived). - The Lookup Hash Table (
StringTablein HotSpot C++): A native hash table that resides in Native Memory (C-Heap) of the JVM operating system process.
flowchart TD
subgraph JVMMemory["JVM Process Memory Topology"]
subgraph JavaHeap["Java Heap (-Xmx)"]
HeapObjs["Regular Application Objects (Users, Orders, Lists)"]
PooledStrings["String Pool Objects ('hello', 'ACTIVE')\nvalue: byte[]"]
end
subgraph NativeMemory["Native Memory (C-Heap)"]
StringTable["HotSpot C++ StringTable\n(ConcurrentHashTable Index)\n[bucket 0] ──► [bucket 1] ──► [bucket 2]"]
Metaspace["Metaspace\n(Klass metadata, Method bytecodes, Constant Pools)"]
end
end
StringTable -.->|Weak References / oop| PooledStrings
Why the String Pool Moved from PermGen to Java Heap
In Java 6, housing the String Pool in PermGen caused severe production stability hazards:
- Inflexible Memory Quota: PermGen had a small, fixed upper bound (defaulting to 64–256 MB via
-XX:MaxPermSize). Runtime interning quickly triggeredOutOfMemoryError: PermGen space. - Infrequent GC Sweeps: Garbage collection in PermGen ran almost exclusively during Full GC pauses, and weak reference tracking for interned strings was inefficient.
- Dynamic Heap Scalability: Relocating the pool to the Java Heap removed the hard memory ceiling—the pool can now scale dynamically to gigabytes under
-Xmx, and unreferenced strings are swept during routine Young/Old GC cycles.
Version-by-Version Comparison
| Characteristic | Java 6 and earlier | Java 7 (7u6+) | Java 8 to 21+ |
|---|---|---|---|
| Object Storage Location | PermGen |
Java Heap |
Java Heap |
| Memory Constraint | -XX:MaxPermSize (64–256 MB) |
-Xmx (Main Heap limit) |
-Xmx (Main Heap limit) |
| OOM Error on Saturation | OOM: PermGen space |
OOM: Java heap space |
OOM: Java heap space |
| GC Collection Behavior | Full GC only (infrequent) | Routine GC cycles (Young/Old) | Concurrent GC thread sweeps |
| Class Metadata Location | PermGen |
PermGen |
Metaspace (Native Memory) |
| HotSpot C++ Table Type | Fixed-size synchronized Hashtable |
Fixed-size synchronized Hashtable |
ConcurrentHashTable (Java 11+, lock-free) |
🔴 Senior Level
HotSpot C++ StringTable Internals
Inside the OpenJDK HotSpot codebase, the String Pool is managed as an off-heap native index:
- Prior to Java 11: Implemented as
StringTable : public Hashtable. Access was synchronized via a global mutex (StringTable_lock). Intensive interning across multiple threads caused severe lock contention and CPU bottlenecks. - Java 11+ (JEP 341: Concurrent StringTable): Rewritten as a lock-free native
ConcurrentHashTable:- Concurrent worker threads execute lock-free lookups and fine-grained insertions.
- Supports dynamic resizing: the JVM automatically increases bucket counts when the load factor exceeds thresholds.
Garbage Collection Lifecycle: Weak Roots & OopStorage
Strings in the String Pool are not permanent (unless tied to a loaded class constant pool):
- Pointers from
StringTableto Java Heap objects are treated by the Garbage Collector as Weak Roots. - In modern HotSpot versions, these pointers are managed through the
OopStorage(Weak Processor) subsystem. - During the GC Marking Phase, the collector traces live object graphs from Strong Roots (thread stacks, JNI handles, static fields).
- If a pooled
Stringobject has no strong incoming references from live application objects, the GC marks it as eligible for reclamation. - During the Weak Processing phase, HotSpot invokes
StringTable::unlink(), removing the native table entry, after which theStringobject and itsbyte[] valueare freed.
Runtime Constant Pool (Metaspace) vs. String Pool (Java Heap)
It is crucial to distinguish between a class’s Runtime Constant Pool and the global String Pool:
- Class Runtime Constant Pool: Stored in Metaspace inside the C++
InstanceKlassstructure. In bytecode, string constants are stored asCONSTANT_String_infoentries pointing to UTF-8 symbol entries. - Literal Resolution: When the
ldc(Load Constant) instruction executes for the first time, HotSpot reads UTF-8 characters from Metaspace, queries the globalStringTablein Native Memory, and locates or allocates ajava.lang.Stringin the Java Heap. The resulting heap pointer is cached in the class’sConstant Pool Cachein Metaspace.
4 Tricky Interview Questions
1. Where are pooled string objects stored versus where the StringTable hash table itself is stored in Java 17/21?
Answer:
There is a distinct memory bifurcation:
- The actual
java.lang.Stringobject wrappers and their internal backingbyte[] valuearrays reside in the Java Heap (typically promoted to Old Generation if long-lived). - The indexing structure—the native HotSpot C++ hash table
StringTable(bucket pointer arrays andConcurrentHashTablemetadata)—resides in Native Memory (C-Heap) of the OS process outside the Java Heap.
2. Can the Garbage Collector reclaim an interned string from the String Pool if dynamicString.intern() was called?
Answer:
Yes, it can.
Entries in the native StringTable are held as Weak Roots via OopStorage. If the local reference String s = new String("data").intern() goes out of scope and no other live objects, static fields, or thread stacks hold a strong reference to that string, the Garbage Collector will reclaim the object during a subsequent GC cycle and unlink its native entry from StringTable.
The only pooled strings that persist indefinitely are compile-time literals tied to classes loaded by the root system classloaders (which are never unloaded).
3. Why do many developers mistakenly believe the String Pool moved to Metaspace in Java 8, and why is this wrong?
Answer:
In Java 8, PermGen was removed and replaced with Metaspace in off-heap native memory for class metadata. Because PermGen historically held both class metadata and the String Pool in Java 6, many assumed everything in PermGen migrated to Metaspace.
This is historically and architecturally incorrect: the String Pool was relocated from PermGen to the Java Heap in Java 7 (Update 6), years before Java 8. When Java 8 debuted, only class metadata moved to Metaspace; the String Pool remained in the Java Heap, where it resides today.
4. What happens if you start the JVM with -XX:StringTableSize=1009 and intern 1,000,000 unique strings in Java 8 versus Java 17?
Answer:
- In Java 8: The bucket count is fixed at 1,009. Interning 1,000,000 strings results in bucket collision chains averaging ~1,000 items. Because Java 8 synchronizes
StringTablewith the globalStringTable_lockmutex, every.intern()call degenerates into an $O(N)$ linear linked-list traversal under a global lock. The JVM encounters massive CPU saturation and thread stalling. - In Java 17: Under JEP 341,
StringTableis aConcurrentHashTable. The JVM dynamically monitors table load factor and automatically resizes bucket arrays in background threads without blocking concurrent readers. The performance degradation is negligible compared to Java 8.
🎯 Interview Cheat Sheet
30-Second Summary
“In modern Java (Java 7u6+ through Java 8, 11, 17, and 21+), the String Pool resides inside the Java Heap. Pooled
Stringobjects share standard heap memory bounded by-Xmxand are garbage-collected when unreferenced. The native indexStringTableresides in Native Memory (C-Heap). The common belief that Java 8 placed the String Pool in Metaspace is a myth: Metaspace holds class metadata off-heap, while the String Pool remains firmly in the Java Heap.”
Key Concepts Checklist
- Memory Evolution: Java 6 (PermGen) $\to$ Java 7u6+ (Java Heap) $\to$ Java 8 (Metaspace for classes, String Pool stays in Heap).
- Memory Layout:
Stringobjects &byte[]arrays live in Java Heap;StringTablehash index lives in Native Memory. - GC Reclaimability: Pooled strings without strong roots are collected via
OopStorageweak root unlinking. - JEP 341 (Java 11+): HotSpot
StringTableconverted from a synchronized fixed-size table to lock-freeConcurrentHashTable. - Diagnostics: Native table density can be inspected via
jcmd <pid> VM.stringtable -verbose.
Common Pitfalls & Red Flags
- ❌ “The String Pool is stored in Metaspace in Java 8 and later.” (Critical architectural error: it is in the Java Heap).
- ❌ “Strings in the String Pool can never be garbage collected.” (False: dynamically interned strings without strong roots are collected).
- ❌ “The String Pool has its own dedicated memory quota flag.” (False: it shares standard heap memory bounded by
-Xmx).
Related Topics
- How String Pool Works — Pool architecture
- Can String Pool Cause OutOfMemoryError — OOM mechanics
- When to Use intern() — Manual interning
- What are Compact Strings in Java 9+ — Compact string encoding