33
Interview question: String a = "ab"; String b = "a" + "b"; Is a == b equal

Interview site

Purpose of the investigation: To investigate the understanding of the basic knowledge of JVM, involving constant pool, JVM runtime data area, etc.

Scope of investigation: Working for 2 to 5 years.

background knowledge

To answer this question, you need to understand the two most basic questions

  1. String a=“ab” , what happened in the JVM?
  2. String b=“a”+“b” , how is the bottom layer realized?

JVM runtime data

First, let's review the runtime data area of the JVM together.

In order for everyone to have a global perspective, I draw the overall structure from class loading to the JVM runtime data area, as shown in the figure below.

The role of each area is explained in detail in my previous interview series, so I won’t repeat it here.

image-20211106144143909

In the above figure, we need to focus on several categories:

  1. String constant pool
  2. Encapsulation class constant pool
  3. Runtime constant pool
  4. JIT compiler

These contents are very closely related to this interview question. For the content of the constant pool part, let me first leave a question, and first follow me to learn about the constant pool in the JVM.

What are the constant pools in the JVM

You often hear various constant pools, but you don't know where these constant pools are stored, so there are many questions: Which constant pools are there in the JVM?

The constant pool in JVM can be divided into the following categories:

  1. Class file constant pool
  2. Global string constant pool
  3. Runtime constant pool

Class file constant pool

There is a constant pool in the bytecode of each Class file, which mainly stores various literals and symbol references generated by the compiler. For a more intuitive understanding, we write the following program.

public class StringExample {
    private int value = 1;
    public final static int fs=101;

    public static void main(String[] args) {
        String a="ab";
        String b="a"+"b";
        String c=a+b;
    }
}

After the above program is compiled, javap -v StringExample.class , and the intercepted part of the content is as follows.

Constant pool:
   #1 = Methodref          #9.#32         // java/lang/Object."<init>":()V
   #2 = Fieldref           #8.#33         // org/example/cl07/StringExample.value:I
   #3 = String             #34            // ab
   #4 = Class              #35            // java/lang/StringBuilder
   #5 = Methodref          #4.#32         // java/lang/StringBuilder."<init>":()V
   #6 = Methodref          #4.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StrvalueingBuilder;
   #7 = Methodref          #4.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Class              #38            // org/example/cl07/StringExample
   #9 = Class              #39            // java/lang/Object
  #10 = Utf8               value
  #11 = Utf8               I
  #12 = Utf8               fs
  #13 = Utf8               ConstantValue
  #14 = Integer            101
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lorg/example/cl07/StringExample;
  #22 = Utf8               main
  #23 = Utf8               ([Ljava/lang/String;)V
  #24 = Utf8               args
  #25 = Utf8               [Ljava/lang/String;
  #26 = Utf8               a
  #27 = Utf8               Ljava/lang/String;
  #28 = Utf8               b
  #29 = Utf8               c
  #30 = Utf8               SourceFile
  #31 = Utf8               StringExample.java
  #32 = NameAndType        #15:#16        // "<init>":()V
  #33 = NameAndType        #10:#11        // value:I
  #34 = Utf8               ab
  #35 = Utf8               java/lang/StringBuilder
  #36 = NameAndType        #40:#41        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #37 = NameAndType        #42:#43        // toString:()Ljava/lang/String;
  #38 = Utf8               org/example/cl07/StringExample
  #39 = Utf8               java/lang/Object
  #40 = Utf8               append
  #41 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #42 = Utf8               toString
  #43 = Utf8               ()Ljava/lang/String;

Let's pay attention to the part described by Constant pool , which represents the constant pool of the Class There are mainly two types of constants stored in the constant pool.

  1. Literal.
  2. Symbol reference.

literal

  • Literals, the way to assign values to basic types of variables is called literals or literals. For example: String a=“b” , where "b" is a string literal, and the same analogy includes integer denominations, floating-point type literals, and character literals.

    In the above code, the bytecode of the literal constant is:

    #3 = String             #34            // ab
    #26 = Utf8               a
    #34 = Utf8               ab
  • Member variables, static variables, instance variables, and local variables modified with final

      #11 = Utf8               I
      #12 = Utf8               fs
      #13 = Utf8               ConstantValue
      #14 = Integer            101

From the above bytecode, the literal and final modified attributes are stored in the constant pool. These literal values in the constant pool refer to the value of the data, such as ab , 101 .

For basic data types, such as private int value=1 , in constant pool only retained his field descriptors (I) and field name (value), it does not exist and the literal constant pool.

  #10 = Utf8               value
  #11 = Utf8               I

In addition, for String c=a+b; , c is not saved to the constant pool, because during compilation, the values of a and b

#29 = Utf8               c
#35 = Utf8               java/lang/StringBuilder
#36 = NameAndType        #40:#41        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#37 = NameAndType        #42:#43        // toString:()Ljava/lang/String;
#39 = Utf8               java/lang/Object
#40 = Utf8               append
#41 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;

If we modify the code to the following form

public static void main(String[] args) {
  final String a="ab";
  final String b="a"+"b";
  String c=a+b;
}

After re-generate bytecode, the bytecode can see changes, c value of this property abab also be saved to the constant pool.

#26 = Utf8               c
#27 = Utf8               SourceFile
#28 = Utf8               StringExample.java
#29 = NameAndType        #12:#13        // "<init>":()V
#30 = NameAndType        #7:#8          // value:I
#31 = Utf8               ab
#32 = Utf8               abab

Symbol reference

symbol reference mainly designed to involve concepts in compilation principles, including the following three types of constants:

  1. Full Qualified Name of the class and interface (Full Qualified Name) , which is Ljava/lang/String; , is mainly used to parse the direct reference of the class at runtime.

      #23 = Utf8               ([Ljava/lang/String;)V
      #25 = Utf8               [Ljava/lang/String;
      #27 = Utf8               Ljava/lang/String;
  2. The name and descriptor of the variable declared in the class or interface, including class-level variables (static) and instance-level variables .

    #1 = Methodref          #9.#32         // java/lang/Object."<init>":()V
    #2 = Fieldref           #8.#33         // org/example/cl07/StringExample.value:I
    #3 = String             #34            // ab
    #4 = Class              #35            // java/lang/StringBuilder
    #5 = Methodref          #4.#32         // java/lang/StringBuilder."<init>":()V
    #6 = Methodref          #4.#36         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StrvalueingBuilder;
    #7 = Methodref          #4.#37         // java/lang/StringBuilder.toString:()Ljava/lang/String;
    #8 = Class              #38            // org/example/cl07/StringExample
      
    #24 = Utf8               args
    #26 = Utf8               a
    #28 = Utf8               b
    #29 = Utf8               c
  3. method name and descriptor , the description of the method is similar to the "method signature" during JNI dynamic registration, that is, parameter type + return value type , such as the following byte code, which means main method and String return type.

      #19 = Utf8               main
      #20 = Utf8               ([Ljava/lang/String;)V
Summary: In the Class file, there are some things that will not change, such as the name of a class, the field name of the class/the data type, the method name/return type/parameter name, constants, literals, etc. These are very important when the JVM interprets and executes the program, so after the compiler compiles the source code into a class file, it will use a part of the byte classification to store these unchanged codes, and these bytes are called constant pools.

Runtime constant pool

runtime constant pool is the runtime manifestation of the constant pool (Constant Pool) of each class or interface.

We know that the loading process of a class will go through: loading, connection (verification, preparation, analysis), initialization process, and in this stage of class loading, you need to do the following things:

  1. Obtain the binary byte stream of this class through the fully qualified name of a class.
  2. java.lang.Class object is generated in the heap memory, which represents the loading of this class as the entry point of this class.
  3. Convert class byte stream into the runtime data structure of the method area (meta space).

And the third point is that converts the static storage structure represented by the class byte stream into the runtime data structure of the method area, which includes the process of class file constant pool entering the runtime constant pool.

Therefore, the function of the runtime constant pool is to store class file. In the parsing stage of the class, these symbol references are converted into direct references (the memory address of the instance object), and the translated direct references are also stored in the runtime Time constant pool. Most of the data in the constant pool of the class

image-20211106202558917

The runtime constant pool is stored in the method area (JDK1.8 meta space), which is shared globally, and different classes share a runtime constant pool.

In addition, the runtime constant pool has a dynamic characteristic, and its content is not all the source and compiled class files. Constants can also be generated through code at runtime and put into the runtime constant pool. For example, the String.intern() method.

String constant pool

The string constant pool is simply a constant pool specifically designed for the String type.

There are two common ways to create a string constant pool.

String a="Hello";
String b=new String("Mic");
  1. a is determined during compilation and will enter the string constant pool.
  2. b this variable, by new keyword to instantiate, new is to create an object instance and initialize the instance, so this string object at runtime to determine the instance created on the heap space.

The string constant pool is stored in the heap memory space, and the creation form is shown in the figure below.

image-20211106235703069

When using String a=“Hello” to create a string object, the JVM first checks whether the string object exists in the string constant pool, and if it exists, it directly returns a reference to the string in the constant pool. Otherwise, a new string will be created in the constant pool and a reference to the string in the constant pool will be returned. (This method can reduce the repeated creation of the same string and save memory, which is also a manifestation of the Flyweight model).

As shown in the figure below, if you String c=“Hello” Hello already exists in the constant pool, you can directly return the reference to the string. (Design of Flyweight Mode in String)

image-20211107001801733

When using String b=new String(“Mic”) to create a string object, due to the immutability of the String itself (subsequent analysis), during the JVM compilation process, Mic will be placed in the constant pool of the Class file. When the class is loaded, it will be Create the string Mic the string constant pool. Then use the new keyword to create a String object in the heap memory and point to a reference to the Mic

As shown in the figure below, if you new String(“Mic”) , since the string constant pool already exists Mic , you only need to create a String object in the heap memory.

image-20211107002344014

To summarize briefly: The reason why the JVM designs the string constant pool separately is some optimizations of the JVM in order to improve performance and reduce memory overhead:

  1. Java language, the String object is the object that occupies the largest space in the memory. Efficient use of strings can improve the overall performance of the system.
  2. When creating a string constant, first check whether the string exists in the string constant pool. If there is, the reference instance is directly returned. If it does not exist, the string is instantiated and placed in the constant pool.
The string constant pool is a reference table of string instances maintained by the JVM. In the HotSpot VM, it is a global table called StringTable. What is maintained in the string constant pool is the reference of the string instance, and the underlying C++ implementation is a Hashtable. The string instances that these maintained references refer to are called "resident strings" or "interned strings" or commonly referred to as "strings that have entered the string constant pool"!

Encapsulation class constant pool

In addition to string constant pools, most of Java's basic types of wrapper classes also implement constant pools. Including Byte,Short,Integer,Long,Character,Boolean

Note that the floating-point data type Float,Double does not have a constant pool.

The constant pool of the encapsulation class is implemented in their respective internal classes, such as IntegerCache (the internal class of Integer It should be noted that these constant pools have ranges:

  • Byte,Short,Integer,Long : [-128~127]
  • Character : [0~127]
  • Boolean : [True, False]

The test code is as follows:

public static void main(String[] args) {
  Character a=129;
  Character b=129;
  Character c=120;
  Character d=120;
  System.out.println(a==b);
  System.out.println(c==d);
  System.out.println("...integer...");
  Integer i=100;
  Integer n=100;
  Integer t=290;
  Integer e=290;
  System.out.println(i==n);
  System.out.println(t==e);
}

operation result:

false
true
...integer...
true
false

The constant pool of the package class is actually the cache instance (not the realization of the JVM virtual machine level) implemented in each package class. For example, in Integer, there is IntegerCache , which caches data instances between -128 and 127 in advance. It means that the data in this interval all use the same data object. This is why in the above program, the result of the judgment == true .

This design is actually an application of the Flyweight model.
private static class IntegerCache {
  static final int low = -128;
  static final int high;
  static final Integer cache[];

  static {
    // high value may be configured by property
    int h = 127;
    String integerCacheHighPropValue =
      sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
    if (integerCacheHighPropValue != null) {
      try {
        int i = parseInt(integerCacheHighPropValue);
        i = Math.max(i, 127);
        // Maximum array size is Integer.MAX_VALUE
        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
      } catch( NumberFormatException nfe) {
        // If the property cannot be parsed into an int, ignore it.
      }
    }
    high = h;

    cache = new Integer[(high - low) + 1];
    int j = low;
    for(int k = 0; k < cache.length; k++)
      cache[k] = new Integer(j++);

    // range [-128, 127] must be interned (JLS7 5.1.7)
    assert IntegerCache.high >= 127;
  }

  private IntegerCache() {}
}

The original intention of the encapsulated constant pool is actually the same as String, which is also for caching frequently used data intervals to avoid the memory overhead of frequently creating objects.

Exploring the problem of string constant pool

In the above constant pool, there are still many issues to be explored regarding the design of the String constant pool:

  1. If a string constant already exists in the constant pool, how do you refer to the same string constant when defining the literal of the same string later? That is, the assertion result of the following code is true .

    String a="Mic";
    String b="Mic";
    assert(a==b); //true
  2. How big is the capacity of the string constant pool?
  3. Why design a separate constant pool for strings?

Why design a separate constant pool for strings?

First, let's look at the definition of String.

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
}

Can be found from the above source code.

  1. The String class is final , which means that the class cannot be inherited.
  2. The member attribute value[] String class is also final , which means that the member attribute cannot be modified.

Therefore, String is immutable, which means that String is created, it cannot be changed. There are several advantages of this design.

  1. Convenient implementation of string constant pool: In Java, because String constants are used a lot, if a String object is created every time a String is declared, it will cause a huge waste of space resources. Java puts forward the concept of String pool, which opens up a storage space String pool in the heap. When initializing a String variable, if the string already exists, it will not create a new string variable, but will return the existing string. A reference to a string that exists. If the string is variable and a string variable changes its value, then the value of the variable pointed to by it will also change, and the String pool will not be able to achieve it!
  2. Thread safety. In a concurrent scenario, multiple threads can read a resource at the same time. It is safe and will not cause competition. However, it is not safe to write to the resource. Immutable objects cannot be written, so multithreading is guaranteed. Security.
  3. Ensure that the hash attribute value does not change frequently. Uniqueness is ensured, so that HashMap can realize the corresponding key-value caching function, so when the object is created, its hashcode can be safely cached without recalculation. This is the reason why Map likes to use String as Key, and the processing speed is faster than other key objects. Therefore, the keys in HashMap often use String.
Note that because of the String , it is very important to facilitate the realization of the string constant pool. At this time, the premise of the string constant pool is realized.

The string constant pool is actually the design of the Flyweight mode. It is similar to the cache design of IntegerCache and Character encapsulated objects provided in the JDK, except that String is a JVM-level implementation.

The allocation of strings, like other object allocations, consumes a high cost of time and space. In order to improve performance and reduce memory overhead, JVM has made some optimizations when instantiating string constants. In order to reduce the number of strings created in the JVM, the string class maintains a string pool. Whenever the code creates a string constant, the JVM first checks the string constant pool. If the string already exists in the pool, the instance reference in the pool is returned. If the string is not in the pool, a string will be instantiated and placed in the pool. Java can perform such optimizations because strings are immutable and can be shared without worrying about data conflicts.

We regard the string constant pool as a cache. When double quotes, first look it up in the string constant pool, and return the reference of the string constant pool directly if found, otherwise create a new one The string constants are placed in the constant pool.

How big is the constant pool?

I think everyone must be as curious as I am, how many constants can the constant pool store?

As we said before, the constant pool is essentially a hash table, and this hash means that it cannot be dynamically expanded. This means that the linked list in a single bucket is very likely to be very long, resulting in performance degradation.

In JDK1.8, the fixed bucket number of this hash table is 60013, we can configure the specified number through the following parameter

-XX:StringTableSize=N

You can add the following virtual machine parameters to print constant pool data.

-XX:+PrintStringTableStatistics

After adding the parameters, run the following code.

public class StringExample {
    private int value = 1;
    public final static int fs=101;

    public static void main(String[] args) {
        final String a="ab";
        final String b="a"+"b";
        String c=a+b;
    }
}

When the JVM exits, the usage of the constant pool will be printed as follows:

SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     12192 =    292608 bytes, avg  24.000
Number of literals      :     12192 =    470416 bytes, avg  38.584
Total footprint         :           =    923112 bytes
Average bucket size     :     0.609
Variance of bucket size :     0.613
Std. dev. of bucket size:     0.783
Maximum bucket size     :         6
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :       889 =     21336 bytes, avg  24.000
Number of literals      :       889 =     59984 bytes, avg  67.474
Total footprint         :           =    561424 bytes
Average bucket size     :     0.015
Variance of bucket size :     0.015
Std. dev. of bucket size:     0.122
Maximum bucket size     :         2

You can see that the total size of the string constant pool is 60013 , where the literal is 889 .

When did the literal enter the string constant pool

String literals, unlike other basic types of literals or constants, are not filled and reside in the string constant pool during the resolve phase of class loading, but are stored in a special form at runtime Run-Time Constant Pool. But only when the string literal is called (such as executing the ldc bytecode instruction on it and adding it to the top of the stack), the HotSpot VM will resolve it and create a corresponding string in the string constant pool. The String instance.

Specifically, it should be when the instruction is executed at 1618899c9cbb30 (the instruction indicates that int, float or String constants are pushed from the constant pool to the top of the stack)

In the HotSpot VM of JDK1.8, this unresolved String literal is called pseudo-string and stored in the runtime constant pool in the form of JVM_CONSTANT_String. At this time, no String instance is created for it. .

At compile time, the string literal is stored in the constant pool of the class file in the form of "CONSTANT_String_info" + "CONSTANT_Utf8_info";

After the class is loaded, the string literal is stored in the Run-time Constant Pool in the form of "JVM_CONSTANT_UnresolvedString(JDK1.7)" or "JVM_CONSTANT_String(JDK1.8)";

When a string literal is used for the first time, the string literal is stored in the String Pool in the form of a real String object.

It can be proved by the following code.

public static void main(String[] args) {
  String a =new String(new char[]{'a','b','c'});
  String b = a.intern();
  System.out.println(a == b);

  String x =new String("def");
  String y = x.intern();
  System.out.println(x == y);
}

The string constructed with new char[]{‘a’,’b’,’c’} does not use the constant pool when compiling, but when calling a.intern() , it abc to the constant pool and returns a reference to the constant pool.

intern() method

In the valueOf method in Integer, we can see that if the passed value i is within the range of IntegerCache.low and IntegerCache.high , the cached instance object is returned IntegerCache.cache

public static Integer valueOf(int i) {
  if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
  return new Integer(i);
}

So, in the String type, since there is a string constant pool, is there a way to achieve a function similar to IntegerCache?

The answer is: intern() method. Since the string pool level virtual machine technology, so String class definition and no similar IntegerCache such object pool, String concept buffer / cell type mentioned only intern () this method.

/**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
public native String intern();

The function of this method is: take the contents of the String to look up the table in the Stringtable, if it exists, return the reference, if it does not exist, save the "reference" of the object in the Stringtable table .

For example, the following program:

public static void main(String[] args) {
  String str = new String("Hello World");
  String str1=str.intern();
  String str2 = "Hello World";
  System.out.print(str1 == str2);
}

The result of the operation is: true.

Implement the logic as shown, str1 by calling str.intern() obtaining the constant pool table to Hello World quoted string, then str2 by literal declare a string constant, since this time Hello World already present in the string constant pool, the It also returns the Hello World , so that str1 and str2 have the same reference address, so the operation result is true .

image-20211107151916196

Summary: The intern method will query whether the current string exists from the string constant pool:

  • If it does not exist, it will put the current string into the constant pool and return the local string address reference.
  • If it exists, return the string address of the string constant pool.

Note that when all string literals are initialized, the intern() method will be called by default.

During this procedure, the reason a==b , because the statement a time, will pass intern() way to find out if there is a string constant pool string Hello , because they do not exist, it is created. The same is true for the b b is declared, it is found that Hello already exists in the character constant pool, so it directly returns the reference of the string constant.

public static void main(String[] args) {
  String a="Hello";
  String b="Hello";
}

OK, after learning here, do you feel that you understand? I have a question to test everyone. What is the result of the following program?

public static void main(String[] args) {
  String a =new String(new char[]{'a','b','c'});
  String b = a.intern();
  System.out.println(a == b);

  String x =new String("def");
  String y = x.intern();
  System.out.println(x == y);
}

The correct answer is:

true
false

The second output is false is understandable, because new String(“def”) will do two things:

  1. def in the string constant pool.
  2. new keyword creates an instance object string and points to a reference to the string constant pool def

And x.intern() def obtained from the string constant pool. Their pointing addresses are different, and I will explain them in detail later.

Why is the first output result of true

The description of the intern() method in the JDK documentation: When calling the intern method, if the constant pool (built in the JVM) already contains the same string, the string in the pool is returned. Otherwise, String object to the pool and return a reference String

In constructing String a when using new char[]{‘a’,’b’,’c’} initialization string (not automatically call intern() , lazy loading string is entered into the constant pool), and is not constructed in a string constant pool abc string instance. So when the a.intern() method is String object will be added to the character constant pool, and a reference String object will be returned, so the reference addresses pointed to by a and b

question answer

Interview question: String a = "ab"; String b = "a" + "b"; Is a == b equal

answer : a==b is equal for the following reasons:

  1. Variables a and b are constant strings, of which b , during compilation, because there are no variable factors, the compiler will directly assign the variable b to ab (this is in the category of compiler optimization, that is, compiling After that, b will be saved to the literal amount in the Class constant pool).
  2. For string constants, a ab will be created in the string constant pool and a reference to the string constant pool will be returned.
  3. For the variable b , when assigning the value ab , first check whether the same string exists in the string constant pool, and if it exists, return the string reference.
  4. Therefore, the references pointed to by a and b are the same, so a==b established.

conclusion of issue

Regarding the content of the constant pool, it will take some time to have a more in-depth and comprehensive understanding.

For example, after reading the above content, everyone thinks that they have a very in-depth understanding of the string constant pool. Yes, let's look at another question:

public static void main(String[] args) {
  String str = new String("Hello World");
  String str1=str.intern();
  System.out.print(str == str1);
}

The above code obviously returns false , the reason is shown in the figure below. Obviously, the reference addresses pointed to by str and str1

image-20211107155237442

But let's modify the above code:

public static void main(String[] args) {
  String str = new String("Hello World")+new String("!");
  String str1=str.intern();
  System.out.print(str == str1);
}

The output of the above program becomes: true . why?

This is also an optimization at the JVM compiler level. Because String is an immutable type, theoretically, the execution logic of the above program is: when string concatenation is performed + , it is equivalent to the string constant pointed to by String HelloWorld taken out, and another ! pointed to by the String variable is added to generate a new object.

Assuming that we are for loop, a large number of objects will be generated. If these objects are not recycled in time, it will cause a very large waste of memory.

So after JVM optimization, it is actually spliced by StringBuilder, that is, only one object instance StringBuilder will be generated, and then append method.

To prove what I said, let's take a look at the bytecode of the above code.

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=4, locals=3, args_size=1
         0: new           #3                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
         7: new           #5                  // class java/lang/String
        10: dup
        11: ldc           #6                  // String Hello World
        13: invokespecial #7                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        16: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: new           #5                  // class java/lang/String
        22: dup
        23: ldc           #9                  // String !
        25: invokespecial #7                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        28: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        31: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        34: astore_1
        35: aload_1
        36: invokevirtual #11                 // Method java/lang/String.intern:()Ljava/lang/String;
        39: astore_2
        40: getstatic     #12                 // Field java/lang/System.out:Ljava/io/PrintStream;
        43: aload_1
        44: aload_2
        45: if_acmpne     52
        48: iconst_1
        49: goto          53
        52: iconst_0
        53: invokevirtual #13                 // Method java/io/PrintStream.print:(Z)V
        56: return

As you can see from the bytecode, a StringBuilder is constructed,

 0: new           #3                  // class java/lang/StringBuilder

Then the string constants append method, and finally the toString() method is called to get a string constant.

16: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
28: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: invokevirtual #10                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;

Therefore, the above code is equivalent to the following form.

public static void main(String[] args) {
  StringBuilder sb=new StringBuilder().append(new String("Hello World")).append(new String("!"));
  String str=sb.toString();
  String str1=str.intern();
  System.out.print(str == str1);
}

Therefore, the result obtained is true .

There are many variants based on this question. For example, let's change it again. What is the result of the following program?
public static void main(String[] args) {
  String s1 = "a";
  String s2 = "b";
  String s3 = "ab";
  String s4 = s1 + s2;
  System.out.println(s3 == s4);
}

The answer is false .

Because the above program is equivalent to s3 and s4 point to different address references, naturally they are not equal.

public static void main(String[] args) {
  String s1 = "a";
  String s2 = "b";
  String s3 = "ab";
  StringBuilder sb=new StringBuilder().append(s1).append(s2);
  String s4 = sb.toString();
  System.out.println(s3 == s4);
}
Summary: Only a clear enough understanding of all the knowledge points related to the string constant pool, no matter how the interview process changes, you can answer accurately, this is the power of knowledge!
Copyright statement: All articles in this blog, except for special statements, adopt the CC BY-NC-SA 4.0 license agreement. Please indicate the reprint from Mic takes you to learn architecture!
If this article is helpful to you, please help me to follow and like. Your persistence is the motivation for my continuous creation. Welcome to follow the WeChat public account of the same name for more technical dry goods!

跟着Mic学架构
810 声望1.1k 粉丝

《Spring Cloud Alibaba 微服务原理与实战》、《Java并发编程深度理解及实战》作者。 咕泡教育联合创始人,12年开发架构经验,对分布式微服务、高并发领域有非常丰富的实战经验。