This article is participating in “Java Theme Month – Java Development Practice”, for details: juejin.cn/post/696826…
This is the 11th day of my participation in Gwen Challenge
String concatenation and creation of case analysis
Case a
String a = "test";
String b = "test";
System.out.println(a.equals(b)); // true
System.out.println(a == b); // true
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(b)); / / 1639705018
Copy the code
-
When a initializes, the object “test” is added to the string constant pool. When B initializes, the object “test” is detected in the string constant pool.
-
A ==b; a==b; a==b;
-
The return value of the system.identityHashCode method is the return value of hashCode() in the argument object, whether or not the hashCode() method is overridden.
Case 2
String a = "test";
String b = new String("test");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(b)); / / 1627674070
Copy the code
-
When a initializes, the object “test” is added to the string constant pool. When B initializes, it first checks that the object “test” is in the string constant pool. It does not need to create an object in the string constant pool.
-
A. quals(b) = true, a==b
Case 3
String a = new String("test");
String b = new String("test");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(b)); / / 1627674070
Copy the code
-
When a is initialized, the “test” object is added to the string constant pool, and then the “test” object is created in the heap, where A points to the “test” object.
-
B initializes the same as A, but unlike in the string constant pool, a and B have the same contents, but still create two objects on the heap;
-
So a. quals(b) = true, a==b = false
Four cases
String a = "test";
String b = "te"+"st";
System.out.println(a.equals(b)); // true
System.out.println(a == b); // true
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(b)); / / 1639705018
Copy the code
Unlike case 1, the value of b is obtained by adding two quotes together, but the result is the same as case 1. Both a and B point to the same object in the string constant pool. This is because strings such as “te”+”st”, which are concatenated by multiple string constants, are considered a string constant at compile time
Case 5
String a = new String("test");
String b = new String("te") + new String("st");
System.out.println(a.equals(b)); // true
System.out.println(a == b); // false
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(b)); / / 1627674070
Copy the code
-
According to the analysis in case 3, the contents of A and B are the same, but they point to different objects. However, the question is how many objects will be created in the string constant pool in this case?
-
The answer should be three: “test”, “te” and “st”; How many objects will be created on the heap? The answer is four: a points to “test”, two anonymous objects “te” and “st”, and B points to “test”
Case 6
String a = "test";
String b = "te";
String c = "st";
String d = b + c;
System.out.println(a.equals(d)); // true
System.out.println(a == d); // false
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(d)); / / 1627674070
Copy the code
The initialization mode of object D seems to jump out of the two initialization modes mentioned above. It is generated by connecting two string objects existing in the string constant pool, but in fact, the initialization mode of object D is just a bit more complicated, and does not break away from the two initialization modes mentioned above. The steps are as follows:
-
B and C are already stored in the string constant pool. Copy b and C objects to the heap.
-
Create a new object d in the heap and assign the values of B and C to D;
That is, in this case three objects are created in the string constant pool and three objects are created in the heap
Case of seven
String a = "test";
final String b = "te";
final String c = "st";
String d = b + c;
System.out.println(a.equals(d)); // true
System.out.println(a == d); // true
System.out.println(System.identityHashCode(a)); / / 1639705018
System.out.println(System.identityHashCode(d)); / / 1639705018
Copy the code
In this case, as in case 4, the compiler treats b+ C as a string constant, so d refers to an existing “test” object in the string constant pool
conclusion
-
As you can see, String initialization under different circumstances does have a direct impact on how the JVM creates objects and where they are created, which validates the previous conclusion
-
If the contents of a compile-time String are determinable (cases 4, 7), the initialization is considered literal anyway.
String, StringBuilder, StringBuffer
-
Strings are immutable strings. All change operations create new objects. StringBuilder and StringBuffer are mutable strings.
-
StringBuilder and StringBuffer are essentially identical in implementation, but most of the methods in StringBuffer are embellished with synchronized
-
String and StringBuffer are thread-safe. StringBuilder is not, because String is immutable and is obviously safe, and methods in StringBuffer are often thread-safe, with the synchronized keyword as its modifier
-
Generally, StringBuilder > StringBuffer > String, but not always
- Use String when the String is little or unchanged
- Use StringBuilder for frequent changes,
- StringBuffer is used if changes are frequent and multiple threads are involved
The + sign concatenates strings through the StringBuilder append and toString methods
Garbage collection
When an object has no reference to point to, the garbage collector collects it. Here’s an example:
public class ImmutableStrings
{
public static void main(String[] args) {
String one = "someString";
String two = new String("someString");
one = two = null; }}Copy the code
When one = two = NULL, only one object is recycled, and String objects always have references from the String constant pool, so they are not recycled
You probably know that string.intern () is called to return the current String if it exists in the constant pool. If the string is not in the constant pool, it is put into the constant pool and then returned.
However, for more complex examples, the result may not be clear, and the result may depend on the JDK version. This article gives you a deeper understanding of string.Intern () and how it works through theory and examples. This is not only a common score in the written interview, but also an attitude towards technical exploration.
Intern method
Constant pool
In addition to descriptions of versions, fields, methods, interfaces, and so on, the Class file contains a constant pool for storing various literal and symbolic references generated at compile time. Where String pooling (aka String normalization) is a single shared String that replaces several objects with the same value but different identities. You can do this yourself via Map
,>
(with soft or weak references as required), or you can use the string.Intern () method provided by the JDK.
String.intern() in Java 6
-
Constant pool in a method in Java 6 and 6 before area (Perm area), the excessive use of intern () can directly produce Java lang. OutOfMemoryError: PermGen space.
-
Because the method area has a fixed size, it cannot be extended at run time. Although this can be set using the -xx :MaxPermSize=N option, the default PermGen size varies between 32M and 96M, depending on the platform.
-
You can increase its size, but its size is still fixed, and this limitation makes the use of string.intern () unmanageable. This is why string pooling in the Java 6 era was mostly implemented in manually managed maps.
String.intern() in Java 7
-
Oracle has made one very important change to the constant pool in Java 7 – the Z string constant pool has been relocated to the heap. This means you are no longer limited to a single fixed-size memory area. All strings are now in the heap, like most other normal objects, which allows you to manage only the heap size as you adjust your application. Technically, this is just a reason to use string.intern (). But there are other reasons.
-
If constants are no longer referenced, then the JVM can reclaim them to save memory, so the constant pool in the heap can be more easily managed by the JVM for garbage collection along with other objects in the heap.
In the previous conclusion, you can see that strings seem to be added to the String constant pool only if the compiler can represent them as String constants, but not if String objects are determined at run time. The intern method provided in the String class is one way to add a String constant pool at run time.
When the intern method is called, if the String constant pool already contains a String object with the same content, the object in the pool is returned. Otherwise, a heap-referenced address of the object is added to the String constant pool and the value of the object’s address is returned (in JDK6, the String is added directly to the pool and the String is returned).
Such as the following cases
String a = "test";
System.out.println(System.identityHashCode(a)); / / 1639705018
a = a.intern();
System.out.println(System.identityHashCode(a)); / / 1639705018
Copy the code
At this point, the position of the object pointed to by a after initialization is in the string constant pool. After executing a = a.intern(), the position pointed to is in the string constant pool. So the hashcode that you see printed is the same;
String a = new String("test");
System.out.println(System.identityHashCode(a)); / / 1639705018
a = a.intern();
System.out.println(System.identityHashCode(a)); / / 1627674070
Copy the code
At this point, after the initialization, the object a points to is on the heap, and there is a String object with the same content in the String constant pool. After executing a = a.intern(), the point position is in the String constant pool.
String a = new String("te") + new String("st");
System.out.println(System.identityHashCode(a)); / / 1639705018
a = a.intern();
System.out.println(System.identityHashCode(a)); / / 1639705018
Copy the code
-
At this time, the position of the object pointed to by A after initialization is on the heap, and there is no String object with the same content in the String constant pool (here refers to test), after executing a = a.int ()
-
Add a reference to the String constant pool that points to a String on the heap, so a still points to a String on the heap. (If this code is executed in JDk6, the result is different – the address of the string constant pool is compared to the heap address)
String creation and concatenation
The creation of a String
Strings are not primitives, but they can be assigned directly by literals, like primitives, or they can generate a string object by new. But there is a fundamental difference between generating a string from a literal assignment and a new:
-
When a string is created by literal assignment, it first looks in the string constant pool to see if the same string already exists. If so, the reference in the stack refers directly to that string. If not, a string is generated in the string constant pool, and references on the stack point to that string.
-
When a string is created using new, an object of the string is generated directly in the heap, and the reference in the stack points to that object.
A String of joining together
Direct multiple string literal values “+” operation, compile phase directly into a string.
// This is equivalent to assigning "hello world"
String s = "hello "+"world";
Copy the code
The way a variable is concatenated with strings
String s1="world";
String s = "hello "+s1;
Copy the code
Decompilation shows that the above code is equivalent to
String s1="world";
StringBuilder sb=new StringBuilder("hello");
sb.append(s1);
String s = sb.toString();
Copy the code
You actually create a StringBuilder, concatenate it with Append (), and then assign toString() toS
Variables are decorated with final
final String s1="world";
String s = "hello "+s1;
Copy the code
If s1 is final, the concatenation is done at compile time, where s1 is replaced with a constant value, or the same as in the first case
String s=new String(“hello “) + new String(“world”);
This is also a StringBuilder splice
public class StringTest01 {
public static void main(String[] args) {
String baseStr = "baseStr";
final String baseFinalStr = "baseStr";
String str1 = "baseStr01";
String str2 = "baseStr"+"01";
String str3 = baseStr + "01";
String str4 = baseFinalStr+"01";
String str5 = new String("baseStr01").intern(); System.out.println(str1 == str2); System.out.println(str1 == str3); System.out.println(str1 == str4); System.out.println(str1 == str5); }}Copy the code
In order:
-
Str1 ==str2 must return true because both str1 and str2 refer to the same reference address in the constant pool.
-
Str3 is concatenated by the nonconstant baseStr and is actually the result of stringBuilder.append(), so it is not equal to str1 and returns false.
-
Str4 is concatenated by the constant baseFinalStr, which is replaced at compile time and is equivalent to literal assignment, so is true.
-
There is already a “baseStr01” string in the constant pool, and both STR5 and STR1 reference it, so return true.
public class InternTest {
public static void main(String[] args) {
String str2 = new String("str") +new String("01");
str2.intern();
String str1 = "str01"; System.out.println(str2==str1); }}Copy the code
Run in Java 1.6 result: false Run in Java 1.7 and later result: true
Since str2 and str1 refer to objects in the heap and strings in the constant pool, respectively, return false.
-
Intern () returns a reference to the constant pool that already exists in the heap, and if it does, returns a reference to the constant pool
-
The difference is that if the corresponding string is not found in the constant pool, the string is no longer copied to the constant pool, but instead a reference to the original string is generated in the constant pool.
So, str2. Intern (); It will generate a reference to “str01” in the heap in the constant pool, which was already in the constant pool at the time of the literal assignment, so return the reference directly, so str1 and str2 both refer to strings in the heap, returning true.
public class InternTest01 {
public static void main(String[] args) {
String str1 = "str01";
String str2 = new String("str") +new String("01"); str2.intern(); System.out.println(str2 == str1); }}Copy the code
Putting the str1 definition first, Java 1.6,1.7 both return false
Because this str2.intern(); At execution time, “STR01” is already in the constant pool, so str1 and STR2 references are different.
The creation of a String object and the placing of a String constant pool
So when exactly is a String created? When does a reference go into the string constant pool? Three concepts of constant pools need to be proposed:
Static constant pool
The Constant Pool table (stored in the Class file), also known as the static Constant Pool, holds the various literal and symbolic references generated by the compiler.
Two important constant types are CONSTANT_String_info and CONSTANT_Utf8_info
Run-time constant pool
The runtime constant pool is part of the method area. The contents of the constant pool table are stored in the runtime constant pool of the method area when the Class is loaded. An important feature of the runtime constant pool is that it is dynamic compared to the Class file constant pool.
String constant pool
-
In the HotSpot VIRTUAL machine, StringTable is used to store references to String objects, that is, to implement a String constant pool. StringTable is essentially a HashSet, so its contents cannot be repeated.
-
In general, to say that a String is stored in a String constant pool means that a reference to that String is stored in a StringTable
Implementation process
-
During the class parsing phase, the virtual machine creates strings and stores references to strings in the String constant pool.
- When a. Java file is compiled into a class file, the string is stored in the constant pool table in the class file like any other constant, corresponding to the CONSTANT_String_info and CONSTANT_Utf8_info types
-
When a class is loaded, the contents of the static constant pool are stored in the runtime constant pool in the method area. The CONSTANT_Utf8_info type is created at class loading time, indicating that the string literals will enter the runtime constant pool of the current class when the class is loaded. But the StringTable (string constant pool) has no reference and no object is generated in the heap; (No initialization)
-
Encountering LDC bytecode instructions (This directive pushes int, float, or String constant values from the constant pool to the top of the stackIf StringTable already has a reference to CONSTANT_String_info, the reference is returned. If it does not, a String object is created in the heap. And holds a reference to the created object in StringTable and returns it;
Here are some concrete examples of this process:
// create a string as a literal
public class test{
public static void main(String[] args){
String name = "HB";
String name2 = "HB"; }}Copy the code
The bytecode code decompiled with Javap is shown below
# 2 = String #14 #14 = utf8 HB... public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=3, args_size=1 0: LDC #2 // String HB 2: astORE_1 3: LDC #2 // String HB 5: astore_2 6: return......Copy the code
-
When compiled into a bytecode file, the literal “HB” is stored in the class static constant pool of constant type CONSTANT_Utf8_info
-
When the class is loaded, it is also loaded into the runtime constant pool in the method area, which can then be used to query StringTable for matching String references (just a shorthand, CONSTANT_Utf8_info also points to a Symbol object).
-
If StringTable (String constant pool) does not have the same reference as CONSTANT_String_info before the first LDC bytecode instruction is encountered, create a String object in the heap and store the reference to the created object in StringTable. And then return;
-
The astore_1 directive stores the returned reference to the local variable name
-
If StringTable (string constant pool) already has the same reference as CONSTANT_String_info during parsing before two LDC bytecode instructions are encountered, simply return it.
-
And saves the reference it returns to the local variable name2 with the astore_2 directive
newCreating a stringpublic class test2{
public static void main(String[] args){
String name = new String("HB");
String name2 = new String("HB"); }}Copy the code
The bytecode code decompiled with Javap is shown below
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=3, locals=3, args_size=1
0: new #2 // class java/lang/String
3: dup
4: ldc #3 // String HB
6: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
9: astore_1
10: new #2 // class java/lang/String
13: dup
14: ldc #3 // String HB
16: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V
19: astore_2
20: return
Copy the code
-
The new directive creates a new String object in the heap and pushes its reference value to the top of the stack. The dUP directive copies the reference value of the new object on the top of the stack and pushes the copy value to the top of the stack. The local variable name holds the reference value.
-
Next, before encountering the first LDC bytecode instruction,Parsing shows that StringTable (string constant pool) does not yet have the same reference as CONSTANT_String_info, creates a String in the heap and stores a reference to the created object in StringTable, so at run time, creates two Strings.