String Pool (String Constant/Memory/Literals Pool) vs Constant Pool
When compiler meets any string literal, compiler puts it into String Constant Pool. All the method or class variables refer to that string constant pool;
class MemoryModel { final String s = "abc"; String s5 = "abc";}
:
String s1 = "abc";
MemoryModel mm = new MemoryModel();
System.out.println("abc".hashCode()); //12345
System.out.println(mm.s.hashCode()); //12345
System.out.println(mm.s5.hashCode()); //12345
System.out.println(s1.hashCode()); //12345
String "abc" will go to String pool, whereas s,s5 will go in constant pool (or Runtime Constant Pool) of class MemoryModel. ‘s1’ is method local variable so it’ll be saved in JVM frame.

- A string literal always refers to the same instance of class String.
- Literal strings in any class of any package represent references to
the same String object.
- Strings computed by constant expressions are computed at compile time and then treated as if they were literals.
Example 2
public method(){
final String s="abc";
String s1="def";
final String s2=s+s1;
String s3=s+"def";
String s4="abc"+"def";
}
For above method nothing will be saved in Class Constant Pool. “abc”, “def”, and “abcdef” will be saved in String pool.
"abcdef".hashCode() == ("abc" + "def").hashCode() //true
Bytecode for above method;
0: ldc #19 // String abc
2: astore_1
3: ldc #21 // String def
5: astore_2
6: new #23 // class java/lang/StringBuilder
9: dup
10: ldc #19 // String abc
12: invokespecial #25 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
15: astore_3
16: aload_3
17: aload_2
18: invokevirtual #28 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
21: invokevirtual #32 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
24: astore 4
26: ldc #36 // String abcdef
28: astore 5
30: ldc #36 // String abcdef
32: astore 6
34: return
Above bytecode suggests that nothing is put into CP (Constant Pool). For above code s3 == s4 and s3 == “abcdef” because all final constants are replaced with their value at the time of compilation. So the above code will be converted to this;
final String s="abc";
String s1="def";
final String s2=new StringBuilder("abc").append(s1).toString();
String s3="abc"+"def";
String s4="abc"+"def";
But if ‘s’ is not final then
String s="abc";
String s1="def";
final String s2=new StringBuilder(s).append(s1).toString();
String s3=new StringBuilder(s).append("def").toString();
String s4="abc"+"def";
In above code, s2 and s3 will be pointing a new instance of String having char[]. So s3 and s4 are not same.
Example 3
public class MemoryModel {
final String s="abc";
String s1="def";
final String s2=s+s1;
String s3=s+"def";
String s4="abc"+"def";
String s5=s2;
}
Bytecode for this is pretty much similar to above bytecode. But you’ll see few more entries of putfield
and getfield
which tells us that constant are put into CP.
Let’s compare byte code for above code;
With Final 's'
21: ldc #8 // String abc
23: invokespecial #27 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
26: aload_0
27: getfield #23 // Field s1:Ljava/lang/String;
30: invokevirtual #30 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
33: invokevirtual #34 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
36: putfield #38 // Field s2:Ljava/lang/String;
Without Final 's'
21: aload_0
22: getfield #18 // Field s:Ljava/lang/String;
25: invokestatic #26 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
28: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
31: aload_0
32: getfield #22 // Field s1:Ljava/lang/String;
35: invokevirtual #35 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
38: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
41: putfield #43 // Field s2:Ljava/lang/String;
And
With Final 's'
39: aload_0
40: ldc #40 // String abcdef
42: putfield #42 // Field s3:Ljava/lang/String;
Without Final 's'
44: aload_0
45: new #24 // class java/lang/StringBuilder
48: dup
49: aload_0
50: getfield #18 // Field s:Ljava/lang/String;
53: invokestatic #26 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
56: invokespecial #32 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
59: ldc #20 // String def
61: invokevirtual #35 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
64: invokevirtual #39 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
67: putfield #45 // Field s3:Ljava/lang/String;
This comparison also proves that the value of final variable is replaced at the time of comparison. And the constant fields are saved into CP. So if ‘s’ is not final then the value of ‘s is taken from CP using getfield.