String immutability in Java
-
Last Updated: December 8, 2024
-
By: javahandson
-
Series
Discover the concept of string immutability in Java, including how strings are stored in the String Pool, ways to create strings, and enhancing performance. A complete guide to understanding the immutability of strings in Java.
A String is an array of characters or a set of characters enclosed in double-quotes. The string is a non-primitive datatype and is considered as an Object.
String str = "Java";
Primitive data types such as int, char, byte, short, boolean, long, float, and double are stored directly in the stack memory. On the other hand, non-primitive data types such as String are stored in the heap memory while their references are kept in the stack memory.
Strings are immutable in nature that means if we alter the value of a string then instead of updating the original object it creates a new object. Immutability is achieved in strings using the String pool concept which we will learn in the below section.
String str = "Java"; // Stored in the String pool area
str = str + " HandsOn" // Stored in the heap area
In the above code when we append a value to str the old Object remains as it is and a new Object gets created in the heap area and str starts pointing to the new value i.e. “Java HandsOn”.
The “Java” literal remains in the String Constant Pool and can still be referenced elsewhere in the program if needed.
The String Pool, also known as the String Constant Pool or String Intern Pool is a specific area in the heap memory where string literals are stored. When a new string literal is created, the JVM checks whether the literal already exists in the String Pool:
If the literal is found its reference is reused and no new object is created.
If the literal is not present it is added to the String Pool and a reference to this newly stored value is returned.
This mechanism helps optimize memory usage by avoiding duplicate string objects.
String str1 = "Java"; // Stored in the String pool area
String str2 = "Java"; // As Java literal is already stored in the pool area hence new Object will not be created and str2 will point to the existing Java literal.
str1 and str2 will point to the same literal in the String Constant Pool.
String str3 = "HandsOn"; // Stored in the String pool area
The String Constant Pool helps optimize memory usage by reducing inefficiencies in the heap.
For example, if there are 100 string literals with the same value without the String Constant Pool, the same literal would be stored 100 times in the heap consuming a significant amount of memory. However, with the String Constant Pool, the literal is stored only once, and all references to it point to the same location in the pool. This approach significantly reduces memory usage.
There are 3 ways to create strings:
A string literal is a value assigned to a variable, enclosed in double quotes (e.g., “Java”). When a string literal is created using the = operator, the literal is stored in the String Constant Pool. If the same literal already exists in the pool, its reference will be reused instead of creating a new object.
String str1 = "Java";
By default, the constant pool area will be empty. Since the String Constant Pool is initially empty, the literal “Java” is added to the pool.
The reference to this literal is assigned to str1.
String str2 = "Java";
The JVM checks if the literal “Java” is already present in the String Constant Pool.
Since it is already present, no new literal is added to the pool. Instead, the existing reference is assigned to str2.
String str3 = "HandsOn";
The JVM checks if the literal “HandsOn” exists in the String Constant Pool.
Since it is not present, the literal “HandsOn” is added to the pool, and its reference is assigned to str3.
package com.java.handson.strings; public class StringImmutability { public static void main(String[] args) { String str1 = "Java"; String str2 = "Java"; String str3 = "HandsOn"; System.out.println("str1 reference : "+System.identityHashCode(str1)); System.out.println("str2 reference : "+System.identityHashCode(str2)); System.out.println("str3 reference : "+System.identityHashCode(str3)); } } Output: str1 reference : 23934342 str2 reference : 23934342 str3 reference : 22307196
System.identityHashCode() – This method generates a hash code based on the memory address of the object, regardless of the object’s content. It is useful for checking whether two references point to the same object.
In the above output, we can see the str1 and str2 references are the same and they point to the same object in the String constant pool area.
We can also create a String object using the new keyword, which is similar to creating any other object in Java.
When the new keyword is used to create a String object the string literal itself is placed in the heap memory rather than the String Constant Pool. This means a new String object is created in the heap even if the same literal already exists in the String pool.
String str1 = "Java"; // This literal gets stored in the String constant pool
The literal “Java” is stored in the String Constant Pool, and the reference to it is assigned to str1.
If the literal “Java” already exists in the pool, its reference will be reused.
String str2 = new String("Java"); // A new object gets created and stored in the heap memory
A new String object is created in the heap memory, and its reference is stored in str2.
The JVM does not check whether “Java” is already present in the String Constant Pool because the new keyword explicitly instructs it to create a new object.
String str3 = new String("Java"); // Again a new object gets created and stored in the heap memory area
Another new String object is created in the heap memory, and its reference is stored in str3.
Similar to Line 2, the JVM does not check for the existence of “Java” in either the String Constant Pool or the heap memory.
package com.java.handson.strings; public class StringImmutability { public static void main(String[] args) { String str1 = "Java"; String str2 = new String("Java"); String str3 = new String("Java"); System.out.println("str1 reference : "+System.identityHashCode(str1)); System.out.println("str2 reference : "+System.identityHashCode(str2)); System.out.println("str3 reference : "+System.identityHashCode(str3)); } } Output: str1 reference : 23934342 str2 reference : 22307196 str3 reference : 10568834
In the above output, we can see all the references point to different memory locations. str1 points to constant pool area whereas str2 and str2 points to the heap memory.
As we discussed earlier, when a String is created using the new keyword, it is stored in the heap memory. However, when the intern() method is called on the string, the JVM checks the String Constant Pool.
If the literal already exists in the pool the intern() method returns the reference to the existing literal.
If the literal is not present the JVM adds it to the String Constant Pool and returns the reference to the newly added string.
String str1 = "Java"; // This literal gets stored in the String constant pool
The literal “Java” is stored in the String Constant Pool, and its reference is assigned to str1.
String str2 = new String("Java").intern(); // Same reference will be returned as "Java" literal exist in constant pool area
Firstly a new String object with the content “Java” is created in the heap memory.
Secondly, the intern() method checks if the literal “Java” exists in the String Constant Pool. Since “Java” is already present in the pool, no new entry is created. The reference to the existing “Java” in the pool is returned and stored in str2.
String str3 = new String("HandsOn"); // Creates a new object in the heap
A new String object with the content “HandsOn” is created in the heap memory, and its reference is assigned to str3.
The JVM does not check the String Constant Pool when using the new keyword.
str3.intern(); // Adds "HandsOn" to the String Constant Pool if not already present and returns its reference
The intern() method checks if the string “HandsOn” exists in the String Constant Pool.
If “HandsOn” is not present it is added to the pool and a reference to it is returned. However, this returned reference is not automatically assigned to str3. If we want str3 to point to the interned string, we must explicitly assign it, like this:
str3 = str3.intern();
package com.java.handson.strings; public class StringImmutability { public static void main(String[] args) { String str1 = "Java"; String str2 = new String("Java").intern(); String str3 = new String("HandsOn"); str3 = str3.intern(); System.out.println("str1 reference : "+System.identityHashCode(str1)); System.out.println("str2 reference : "+System.identityHashCode(str2)); System.out.println("str3 reference : "+System.identityHashCode(str3)); } } Output: str1 reference : 23934342 str2 reference : 23934342 str3 reference : 22307196
In the above output, we can see that str1 and str2 point to the same memory location in the constant pool area and as str3 has a different value hence it points to a different location in the constant pool area.
So this is all about the immutability of strings in Java. If you have any questions on this topic, please raise them in the comments section. If you liked this article then please share this post with your friends and colleagues.