π§© How Strings Fit into the JVMβs Memory Model¶
1οΈβ£ Big Picture β The Three Key Memory Zones¶
When the JVM runs, it divides its runtime memory roughly like this:
+-----------------------------------------------+
| JVM Process Memory |
| |
| ββββββββββββββββ ββββββββββββββββ |
| | Heap | | Metaspace | |
| | (objects) | | (class info)| |
| ββββββββββββββββ ββββββββββββββββ |
| β² β² |
| β β |
| β β |
| Object instances Class<?> blueprints |
| |
| +-----------------------------------------+ |
| | Stack (per thread) | |
| | β holds local vars, call frames, refs | |
| +-----------------------------------------+ |
+-----------------------------------------------+
- Heap: where live objects (like
new User()ornew String()) live. - Metaspace: where class metadata (
Class<?>) is stored. - Stack: where temporary references and method calls are tracked per thread.
So far so good.
Now β where does the βString poolβ live in all this?
2οΈβ£ The String Pool β a Special Corner of the Heap¶
When you write:
you donβt get two String objects.
Both s1 and s2 point to the same one.
Thatβs because of the String pool β a cache of unique string literals maintained by the JVM inside the heap (specifically in a small internal structure sometimes called the interned string table).
π§ Why it exists¶
Strings are everywhere in code β class names, annotations, log messages, JSON keys. If every identical literal created a new object, memory would explode.
The pool ensures that identical string literals share one object.
π§ How it works¶
- When the JVM loads a class from bytecode, it parses constant values (including string literals).
-
For every
"Hello"literal, it checks a hidden table in the heap. -
If
"Hello"exists already β reuse the same object. - If not β create it once and store it in that table.
- That table is called the string intern pool.
π§© Visual idea¶
Heap
βββ String Pool
β βββ "Hello" β shared
β βββ "World" β shared
βββ Other Objects
β βββ new User()
β βββ new ArrayList()
Both
point to the same "Hello" inside the pool.
3οΈβ£ How it connects to Objects and Classes¶
| Concept | Description | Where it lives |
|---|---|---|
String object |
an instance of java.lang.String |
Heap |
"literal" |
loaded and pooled at class loading | Heap (string pool) |
String.class |
the blueprint of all String objects | Metaspace |
ClassLoader |
loads java.lang.String at startup |
Bootstrap classloader |
String s = "abc"; |
references a pooled String | Heap (shared entry) |
new String("abc") |
explicitly makes a new object | Heap (outside the pool) |
So, String behaves like any other Object,
but itβs interned β meaning the JVM manages a special table of βknown identical values.β
4οΈβ£ The role of intern()¶
You can manually play with the pool:
String a = new String("Hello");
String b = a.intern();
System.out.println(a == b); // false (a is new)
System.out.println(b == "Hello"); // true (pooled)
new String("Hello")β creates a brand-new object on the heap, outside the pool.-
a.intern()β checks the pool: -
if
"Hello"exists β return reference to that pooled one, - otherwise add it and return it.
So intern() bridges your runtime-created strings back into the shared pool.
5οΈβ£ Historical context β PermGen vs Metaspace¶
Before Java 8, the string pool lived in PermGen (an older memory area used for class metadata).
This caused frequent OutOfMemoryError: PermGen space issues when too many strings were interned.
In Java 8+, the pool was moved to the heap,
and PermGen was replaced by Metaspace, which stores class metadata (Class<?> objects).
Thatβs why today:
Different worlds, but both part of the JVM runtime memory.
6οΈβ£ Relation to Class Loaders¶
When a class is loaded, all its string literals are loaded along with it. So the ClassLoader that loads your class also triggers the JVM to register its string constants into the pool.
Example:
-
When
Demo.classis loaded: -
The
ClassLoaderparses bytecode constants. - Finds
"Hello"β checks the string pool. - Interns it (adds to the shared table if not already there).
Thatβs why string literals get pooled automatically at class load time.
7οΈβ£ Putting it all together β unified model¶
JVM MEMORY MAP
+------------------------------------------------------------+
| JVM Memory |
|------------------------------------------------------------|
| Heap |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β Object Instances (new User(), new ArrayList(), ...) β |
| β String Pool: "Hello", "World", "abc", ... (shared) β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| |
| Metaspace |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β Class<?> objects for: User, String, ArrayList, etc. β |
| β Each describes structure & behavior of its type. β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| |
| Stack (per thread) |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
| β Method frames, locals, references to heap objects. β |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ |
+------------------------------------------------------------+
Arrows of connection:
- Every object on the heap knows its
Class<?>in metaspace. - Every string literal is shared through the string pool (inside the heap).
- Class loaders read class files and register both Class<?> metadata and string literals.
π§© TL;DR Summary¶
| Concept | Description | Memory Area |
|---|---|---|
Object |
Live instance with fields | Heap |
Class<?> |
Metadata blueprint | Metaspace |
ClassLoader |
Reads .class bytes, defines Class<?> |
Code area β Metaspace |
String literal |
Interned, shared value | Heap (string pool) |
new String() |
Non-pooled string | Heap (normal object) |
intern() |
Returns pooled version of a string | Heap (shared table) |
πͺ One-sentence anchor¶
The String pool is not a magical dimension β itβs just a shared cache of
Stringobjects in the heap, built automatically when classes are loaded, whileClass<?>lives separately in metaspace describing how strings and all other objects exist.