Understanding JVM String Constant Pool and String Interning

String Fundamentals and Internal Structure

The String class in Java is declared as final, meaning it cannot be extended. It implements both Serializable and Comparable interfaces, enabling it to support serialization and comparison operations. Prior to Java 8, strings were internally represented using a char[]. Starting from Java 9, this was changed to a byte[] along with an encoding flag to optimize memory usage.

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    @Stable
    private final byte[] value;
}

This change allows Java to use more compact representations for strings. For example, Latin-1 encoded characters occupy only one byte, while UTF-16 characters (including most Unicode characters) use two bytes per character.

Immutability of Strings

Strings in Java are immutable, meaning any operation that appears to modify a string actually creates a new String object. Consider the following code:

@Test
public void testImmutability() {
    String s1 = "abc";
    String s2 = "abc";
    s1 = "hello";
    System.out.println(s1 == s2); // false
    System.out.println(s1); // hello
    System.out.println(s2); // abc
}

When s1 is reassigned to "hello", a new object is created in the string pool, and s1 now references this new object, leaving the original unchanged.

String Constant Pool and Hashtable Internals

The string constant pool is implemented as a fixed-size hashtable with a default size of 1009. In Java 6 and earlier, this size was fixed and could not be changed. Starting from Java 7, the default size was increased to 60013, and in Java 8, 1009 became the minimum allowed size.

Excessive strings in the pool can lead to hash collisions, which degrade performance, especially during intern() calls. You can adjust the size of the string pool using the -XX:StringTableSize JVM option.

Memory Allocation and Constant Pool

Strings, like primitive types, benefit from a caching mechanism known as the constant pool. This pool is managed by the JVM and helps reduce memory usage by reusing existing string instances.

  • Java 6 and earlier: The constant pool resides in the permanant generation.
  • Java 7 and later: The constant pool is moved to the Java heap, improving garbage collection and memory tuning flexibility.

Using String.intern() can be beneficial in Java 7 and later for optimizing memory usage when dealing with many duplicate strings.

String Concatenation Behavior

String concatenation behavior depends on whether the operands are constants or variables:

  • Concatenation of constants is optimized at compile time.
  • Concatenation involving variables uses StringBuilder at runtime.
  • If a concatenation result is passed to intern(), it may be added to the constant pool.
@Test
public void testConcatenation() {
    String s1 = "a" + "b" + "c"; // compile-time optimization
    String s2 = "abc";
    System.out.println(s1 == s2); // true

    String s3 = "javaEE";
    String s4 = s3 + "hadoop"; // runtime concatenation
    System.out.println(s4 == "javaEEhadoop"); // false
}

Efficiency of StringBuilder

Repeated concatenation using the + operator is inefficient due to the creation of intermediate StringBuilder and String objects. Using StringBuilder directly is significantly more efficient:

@Test
public void testEfficiency() {
    long start = System.currentTimeMillis();
    method1(100000); // ~4000ms
    long end = System.currentTimeMillis();
    System.out.println("Time: " + (end - start));
}

public void method1(int count) {
    String s = "";
    for (int i = 0; i < count; i++) {
        s += "a"; // creates new objects each iteration
    }
}

public void method2(int count) {
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < count; i++) {
        sb.append("a");
    }
}

Interning and Memory Optimization

The intern() method allows explicit control over the string constant pool. It ensures that only one copy of a particular string exists in memory, which is especially useful in applications handling large volumes of duplicate strings.

@Test
public void testInterning() {
    String s1 = new String("ab").intern();
    String s2 = "ab";
    System.out.println(s1 == s2); // true
}

In Java 6, intern() copies the string into the permanent generation if not already present. From Java 7 onward, it stores a reference to the heap string in the constant pool, avoiding duplication.

Garbage Collection of String Pool

Starting with Java 8, since the string pool resides in the heap, it is subject to regular garbage colection. This helps reclaim memory from unused interned strings, improving memory efficiency.

// Enable string pool statistics and GC details
// -XX:+PrintStringTableStatistics -XX:+PrintGCDetails
public class StringGCTest {
    public static void main(String[] args) {
        for (int i = 0; i < 100000; i++) {
            String.valueOf(i).intern();
        }
    }
}

String Deduplication in G1 Garbage Collector

Starting from Java 8, the G1 garbage collector supports automatic string deduplication to reduce memory footprint. This feature is enabled using the following JVM options:

  • -XX:+UseStringDeduplication – Enables deduplication.
  • -XX:+PrintStringDeduplicationStatistics – Prints deduplication statistics.
  • -XX:StringDeduplicationAgeThreshold – Sets the age threshold for deduplication eligibility.

Deduplication works by identifying duplicate character arrays and replacing them with references to a shared array, reducing memory usage significantly in applications with many repeated strings.

Tags: java string StringPool StringInterning StringDeduplication

Posted on Thu, 03 Sep 2026 16:45:50 +0000 by subhuman