Sitemap

Deep Dive into Java String Memory Management

3 min readSep 4, 2025

--

If you’ve written any Java code, chances are you’ve worked with Strings. But have you ever wondered how Strings are stored in memory, or why "Hello" == "Hello" is sometimes true and sometimes false?

Let’s break it down step by step — with code, diagrams, and JVM internals — so you’ll never be confused again.

Strings in Java

  • Strings in Java are immutable. Once created, they cannot be changed.
  • JVM manages Strings in two main areas:
  1. String Pool (String Constant Pool, SCP) → stores unique literals, reused across the program.
  2. Heap memory (outside the pool) → stores new String(...) objects.

This design makes Strings memory efficient and thread-safe.

Example Code

public class StringMemoryExample {
public static void main(String[] args) {
String s1 = "Hello";
String s2 = "Hello";
String s3 = new String("Hello");
String s4 = s3.intern();

System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1 == s4); // true
}
}

Step-by-Step Execution in JVM

Get Sathiswaran’s stories in your inbox

Join Medium for free to get updates from this writer.

Let’s see what happens behind the scenes for each line.

Step 1: String s1 = "Hello";

  • JVM checks the String Pool.
  • "Hello" not found → creates a new pooled object.
  • s1 points to it.
Pool:  "Hello"  ← s1

Step 2: String s2 = "Hello";

  • JVM checks pool again.
  • "Hello" already exists → reuses the same object.
  • s2 points to the same reference.
Pool:  "Hello"  ← s1, s2

✅ s1 == s2 → true

Step 3: String s3 = new String("Hello");

  • new String(...) forces a new object in heap memory.
  • Even though "Hello" comes from the pool, a copy is made.
Pool:  "Hello"  ← s1, s2
Heap: new "Hello" ← s3

❌ s1 == s3 → false (different objects)
✅ s1.equals(s3) → true (same characters)

Step 4: String s4 = s3.intern();

  • intern() checks the pool: "Hello" already exists.
  • Returns the pooled reference.
  • s4 now points to the same object as s1 and s2.
Pool:  "Hello"  ← s1, s2, s4
Heap: new "Hello" ← s3

✅ s1 == s4 → true
❌ s3 == s4 → false

Why this design?

  • Immutability → Safe to share Strings across threads.
  • Pooling → Saves memory by reusing common values.
  • Interning → Lets you control canonical representations (useful in large datasets).

Extra Nuances You Should Know

Compile-time constants are pooled

String a = "He" + "llo";   // optimized to "Hello"
String b = "Hello";
System.out.println(a == b); // true

Runtime concatenation is not pooled

String x = "He";
String y = x + "llo"; // runtime build → new object
System.out.println(y == "Hello"); // false
System.out.println(y.intern() == "Hello"); // true

new String("Hello") always creates a new object

→ Avoid unless you explicitly need a distinct reference.

Java 9+ uses Compact Strings

Strings internally store characters in a byte[] (Latin-1 or UTF-16) for better memory use.

Mental Picture (All Steps Together)

Step 1: s1 = "Hello"
Pool: ["Hello"] ← s1

Step 2: s2 = "Hello"
Pool: ["Hello"] ← s1, s2

Step 3: s3 = new String("Hello")
Pool: ["Hello"] ← s1, s2
Heap: [new "Hello"] ← s3

Step 4: s4 = s3.intern()
Pool: ["Hello"] ← s1, s2, s4
Heap: [new "Hello"] ← s3

Key Takeaways

  • "Hello" (literal) → goes to the pool.
  • new String("Hello") → creates a new object on the heap.
  • intern() → ensures you get the pooled reference.
  • Use literals for efficiency; use intern() when managing duplicates in large datasets.

--

--