The string pool and interning

JVM · lesson 19 of 34 · 3 min read

Literals are shared, anything built at run time is not, and that is the whole == confusion.

Open this lesson in the learning hub

Key points

  • Every string literal is interned. The constant appears once per class file and every use points at the same object.
  • javac folds constant expressions, so "ja" + "va" is already "java" in the class file, and interned too.
  • Concatenating anything non-constant builds a new object every time, which is exactly when == starts returning false.
  • intern() returns the pooled instance for an equal string, adding it to the pool if it was not there yet.
  • The pool is a native hash table sized by -XX:StringTableSize; the string objects themselves live on the ordinary heap.
  • Compare with equals. The only honest use of == on strings is a deliberate identity check.

Example

public class Main {
    public static void main(String[] args) {
        String literal = "java";
        String folded  = "ja" + "va";             // two literals: javac folds this at compile time
        String fresh   = new String("java");      // deliberately a brand new object
        String prefix  = "ja";                    // not final, so not a constant
        String built   = prefix + "va";           // built at run time, in a StringBuilder
        String pooled  = built.intern();          // ask the pool for the shared instance

        System.out.println("literal == folded : " + (literal == folded)  + "   folded into the same constant");
        System.out.println("literal == fresh  : " + (literal == fresh)   + "  new String() always allocates");
        System.out.println("literal == built  : " + (literal == built)   + "  concatenated at run time");
        System.out.println("literal == pooled : " + (literal == pooled)  + "   intern() returned the pooled one");
        System.out.println();
        System.out.println("equals() is true for every one of them : "
                + (literal.equals(folded) && literal.equals(fresh)
                        && literal.equals(built) && literal.equals(pooled)));
        System.out.println();
        System.out.println("identity hashes - a different number means a different object:");
        System.out.println("  literal " + Integer.toHexString(System.identityHashCode(literal)));
        System.out.println("  fresh   " + Integer.toHexString(System.identityHashCode(fresh)));
        System.out.println("  built   " + Integer.toHexString(System.identityHashCode(built)));
        System.out.println("  pooled  " + Integer.toHexString(System.identityHashCode(pooled)));
    }
}

Literals are shared. Anything built at run time is a new object.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the JVM course, and every lesson in it is listed on the JVM contents page.