Q24 of 40 · Core Java

What is the difference between String pool and heap memory for String objects?

Core JavaMidjava-stringsstring-poolheap-memoryinterningjava-memory

Short answer

Short answer: String literals and strings produced by intern() are stored in the String pool (inside the heap in Java 7+), where identical strings share a single object. Strings created with `new String()` are always separate heap objects even if their content matches a pooled string. Pool lookups are O(1) hash lookups.

Detail

String pool (string intern pool): a hash table maintained by the JVM inside the heap (moved from PermGen to main heap in Java 7). When the compiler encounters a string literal like "hello", it checks the pool. If "hello" is already there, it reuses the reference; if not, it adds a new entry. This means two variables assigned "hello" literally point to the exact same object, and == returns true (the Integer cache analogy).

Heap strings: new String("hello") always allocates a fresh object in the regular heap, even if "hello" is already in the pool. The new object has its own identity — == on two new String("hello") instances returns false. This was the old way to bypass pooling (for security: password strings you wanted to scrub by zeroing a char[]), but since String became immutable and the pool moved to the main heap, this pattern is mostly obsolete.

String.intern(): explicitly checks the pool for a matching string and returns the canonical pooled version. Useful when you're generating millions of short strings (e.g., parsing log files) and want deduplication — but it can cause pool contention under heavy multi-threaded use.

For test engineers: the most important practical implication is the == trap when comparing strings returned by API responses, parsed JSON, or built at runtime — none of these are pooled. Always use .equals(). The pool also explains why assertThat(response.field()).isEqualTo("expected") works correctly (AssertJ calls .equals()), but a hand-rolled if (response.field() == "expected") is a latent bug.

// EXAMPLE

StringPool.java

// String literals — pooled, share reference
String a = "hello";
String b = "hello";
System.out.println(a == b);       // true  — same pooled object
System.out.println(a.equals(b));  // true  — same content

// new String() — always a new heap object
String c = new String("hello");
String d = new String("hello");
System.out.println(c == d);       // false — different heap objects
System.out.println(c.equals(d));  // true  — same content

// String.intern() — look up or add to pool
String e = new String("hello").intern();
System.out.println(a == e);       // true  — intern() returns pooled ref

// Concatenation at runtime — NOT pooled
String prefix = "hel";
String suffix = "lo";
String combined = prefix + suffix; // new heap object
System.out.println(a == combined); // false — NOT the pooled "hello"
System.out.println(a.equals(combined)); // true — same content

// Compile-time constants ARE pooled (compiler folds them)
String constant = "hel" + "lo"; // compiler folds to "hello" literal
System.out.println(a == constant); // true — folded at compile time

// WHAT INTERVIEWERS LOOK FOR

Where the pool lives (main heap since Java 7), how literals are pooled, that new String() bypasses the pool, that runtime concatenation is never pooled, and always-use-equals as the practical takeaway. The compile-time constant folding nuance is a strong bonus.

// COMMON PITFALL

Saying 'the string pool is in PermGen'. That was true before Java 7. Since Java 7, the pool is part of the main heap and is subject to normal GC — unreferenced pooled strings can be collected. This change fixed the PermGen OOM errors that plagued Java 6 apps that interned large numbers of strings.