Parsing JVM constant pool with a depth of more than 1W words (the most detailed and deep in the whole network)

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

Interview investigation point

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

Scope of investigation: working for 2 to 5 years.

background knowledge

To answer this question, we need to understand two basic questions

  1. String a = "ab", what happens in the JVM?
  2. String b = "a" + "b", how is the underlying layer implemented?

Runtime data for the JVM

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

In order to give you a global perspective, I draw the overall structure from class loading to JVM runtime data area, as shown in the following figure.

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

In the figure above, 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 closely related to this interview question. Here, for the content of constant pool, first leave a question. First follow me to learn about constant pool in JVM.

What constant pools are there in the JVM

You often hear about various constant pools, but you don't know where these constant pools are stored. Therefore, you will have many questions: what constant pools are there in the JVM?

Constant pools in the 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 literal 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 compiling the above program, view the bytecode file of this class through javap -v StringExample.class, and the intercepted part 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 focus on the Constant pool description, which represents the Constant pool of the Class file. Two types of constants are stored in the Constant pool.

  1. Literal.
  2. Symbol reference.

Literal

  • Literal, the way to assign values to basic type variables is called literal or literal. For example, String a = "b", where "b" is the literal value of string, and so on, including integer value, floating point type literal value and character literal value.

    In the above code, the byte code of 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, such as:

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

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

For basic data types, such as private int value=1, only its field descriptor (I) and field name (value) are reserved in the constant pool, and its literal quantity will not exist in the constant pool.

  #10 = Utf8               value
  #11 = Utf8               I

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

#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 so, 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 the bytecode is regenerated, you can see that the bytecode has changed, and the value abab of c is also 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

Symbolic reference mainly involves the concepts of compilation principle, including the following three types of constants:

  1. The fully qualified name of the class and interface, that is, Ljava/lang/String;, It is mainly used to resolve the direct reference of the class at run time.

      #23 = Utf8               ([Ljava/lang/String;)V
      #25 = Utf8               [Ljava/lang/String;
      #27 = Utf8               Ljava/lang/String;
  2. The name and descriptor of the field, that is, the variables 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. The name and descriptor of the method. The description of the method is similar to the "method signature" during JNI dynamic registration, that is, parameter type + return value type. For example, the following bytecode represents the 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 a class name, class field name / data type, method name / return type / parameter name, constant, literal, 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 classify and store these unchanged code with some bytes, and these bytes are called constant pool.

Runtime Constant Pool

The runtime constant pool is the runtime representation of the constant pool of each class or interface.

As we know, the loading process of a class will go through the processes of loading, connecting (validation, preparation, parsing) and initialization. At the stage of class loading, the following things need to be done:

  1. Get the binary byte stream of a class through its fully qualified name.
  2. Generate a java.lang.Class object in the heap memory, which represents the loading of this class and serves as the entry of this class.
  3. The static storage structure of class byte stream is transformed into the runtime data structure of method area (meta space).

The third point is that the process of transforming the static storage structure represented by the class byte stream into the runtime data structure of the method area includes the process of entering the runtime constant pool from the class file constant pool.

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

The runtime constant pool is saved in the method area (JDK1.8 meta space). It is shared globally. Different classes share a runtime constant pool.

In addition, the runtime constant pool is dynamic, and its contents are not all from the compiled class file. You can also generate constants through code and put them into the runtime constant pool at runtime, such as String.intern() method.

String constant pool

String constant pool is simply a constant pool designed specifically for string types.

There are two common ways to create string constant pools.

String a="Hello";
String b=new String("Mic");
  1. a this variable, which has been determined during compilation, will enter the string constant pool.
  2. b this variable is instantiated through the new keyword. New creates an object instance and initializes the instance. Therefore, this string object can be determined at runtime, and the created instance is in heap space.

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

When using String a = "Hello" to create a string object, the JVM will first check whether the string object exists in the string constant pool. If so, it will directly return the reference of the string in the constant pool. Otherwise, a new string is created in the constant pool and a reference to the string in the constant pool is returned. (this method can reduce the repeated creation of the same string and save memory, which is also the embodiment of the meta sharing mode).

As shown in the following figure, if you create a String through String c = "hello" and find that the String Hello already exists in the constant pool, you can directly return the reference of the String. (shared element pattern design in String)

When using String b=new String("Mic") to create a String object, due to the immutability of String itself (subsequent analysis), Mic will be put into the constant pool of Class file during JVM compilation. When the Class is loaded, Mic will be created in the String constant pool. Next, use the new keyword to create a String object in heap memory and point to a reference to the Mic String in the constant pool.

As shown in the following figure, if you create a String object through new String("Mic"), at this time, because Mic already exists in the String constant pool, you only need to create a String object in heap memory.

A brief summary: the reason why the JVM designs the string constant pool separately is some optimizations of the JVM to improve performance and reduce memory overhead:

  1. As an important data type in Java language, String object occupies the largest space in memory. Using strings efficiently 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 so, directly return the reference instance. If not, instantiate the string and put it into the constant pool.

String constant pool is a reference table of a string instance maintained by the JVM. In HotSpot VM, it is a global table called StringTable. The string instance reference is maintained in the string constant pool, and the underlying C + + implementation is a Hashtable. The string instances referred to by these maintained references are called "resident string" or "interned string" or "string entering the string constant pool"!

Encapsulation class constant pool

In addition to string constant pool, most of the encapsulated classes of Java basic types also implement constant pool. Including byte, short, integer, long, character and Boolean

Note that the floating-point data types float and double have no constant pool.

The constant pool of encapsulated classes is implemented in their own internal classes, such as integercache (internal class of integer). Note that these constant pools are scoped:

  • 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 results:

false
true
...integer...
true
false

The constant pool of encapsulation classes is actually the cache instances implemented in each encapsulation class (not the JVM virtual machine level implementation). For example, in Integer, there is IntegerCache, which caches data instances between - 128 ~ 127 in advance. This means that the data in this interval adopts the same data object. This is why in the above program, the result obtained by = = judgment is true.

This design is actually the application of the meta 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 design intention of encapsulation class constant pool is actually the same as String. It is also used to cache frequently used data intervals to avoid the memory overhead of frequently creating objects.

Exploration on string constant pool

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

  1. If a string constant already exists in the constant pool, how does it refer to the same string constant when defining the literal quantity 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 large is 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
}

It can be found from the above source code.

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

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

  1. Convenient implementation of string constant pool: in Java, because a large number of string constants are used, if a string object is created every time a string is declared, it will cause a great 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 reference of the existing string. If the string is variable and a string variable changes its value, the value of the variable it points to will also change. String pool cannot be implemented!
  2. Thread safety: in a concurrent scenario, it is safe for multiple threads to read a resource at the same time, which will not cause competition. However, it is unsafe to write resources, and immutable objects cannot be written, so the safety of multiple threads is guaranteed.
  3. Ensure that the hash attribute value will not change frequently. The uniqueness is ensured, so that similar HashMap containers can realize the corresponding Key value caching function. Therefore, when creating objects, their hashcode can be cached safely without recalculation. This is 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 the immutability of String makes it easy to implement String constant pool, which is the premise of implementing String constant pool.

String constant pool is actually the design of shared meta mode. It is similar to the cache design of encapsulated objects such as IntegerCache and Character provided in JDK, except that string is the implementation of JVM level.

String allocation, like other object allocation, consumes a high cost of time and space. In order to improve performance and reduce memory overhead, the JVM makes 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 will first check 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 is instantiated and placed in the pool. Java can perform such optimization because strings are immutable and can be shared without worrying about data conflicts.

We regard the string constant pool as a cache. When defining a string constant through double quotation marks, we first look it up from the string constant pool. If we find it, we will directly return the reference of the string constant pool. Otherwise, we will create a new string constant and put it in the constant pool.

How big is the constant pool?

I think you must be as curious as I am. How many constants can the constant pool store?

As we said earlier, the constant pool is essentially a hash table, which indicates that it cannot be dynamically expanded. This means that the linked list in a single bucket is very long, resulting in performance degradation.

In JDK1.8, the number of fixed buckets in 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 parameter to print the data of the constant pool.

-XX:+PrintStringTableStatistics

After adding 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 is 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, of which the literal is 889.

When did the literal enter the string constant pool

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

Specifically, it should be when the ldc instruction is executed (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 JDK 1.8, this kind of string literal that is not really resolve d is called pseudo string, which is based on the JVM_CONSTANT_String is stored in the runtime constant pool, and no string instance is created for it at this time.

During compilation, 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 runtime constant pool in the form of "jvm_constant_unresolved string (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 constant pool as 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 at compile time, but saves abc to the constant pool and returns the reference of the constant pool when calling a.intern().

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 directly from 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 any method to implement functions similar to IntegerCache?

The answer is: the intern() method. Since String pool is a virtual machine level technology, there is no object pool such as IntegerCache in the String class definition. The concept of cache / pool mentioned in the String class is only the intern() 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 to get the content of String and look up the table in Stringtable. If it exists, it returns a reference. If it does not exist, it saves the "reference" of the object in Stringtable.

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 running is: true.

The implementation logic is shown in the figure below. str1 obtains the reference of Hello World string in the constant pool table by calling str.intern(), and then str2 declares a string constant in literal form. Since Hello world already exists in the string constant pool, the reference of the string constant Hello world is returned in the same way, so that str1 and str2 have the same reference address, So that the running result is true.

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, the string address of the string constant pool is returned.

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

The reason why a==b in this program is that when declaring a, it will use the intern() method to find out whether the string Hello exists in the string constant pool. If it does not exist, it will create one. Similarly, the same is true for variable b. therefore, when declaring, b finds that hello's string constant 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 you understand? I'll give you a question. What's the result of this 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

It is understandable that the second output is false, because new String("def") does two things:

  1. Create a string 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.

x.intern() is a reference to def obtained from the string constant pool. Their pointing addresses are different, which will be explained in detail later.

Why is the first output true?

Description of the intern() method in the JDK document: when calling the intern method, if the constant pool (built-in in the JVM) already contains the same String, the String in the pool will be returned. Otherwise, add the String object to the pool and return a reference to the String object.

When building String a, when initializing the String with new char [] {'a', 'b', 'c'} (intern() will not be called automatically, and the String enters the constant pool by lazy loading), the String instance abc is not built in the String constant pool. Therefore, when the a.intern() method is called, the String object will be added to the character constant pool and the reference to the String object will be returned. Therefore, the reference addresses pointed to by a and b are the same.

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. Since there are no changeable factors for variable b during compilation, the compiler will directly assign variable b to ab (this belongs to the scope of compiler optimization, that is, after compilation, b will be saved to the Class constant pool).
  2. For string constants, when initializing a, a string ab is created in the string constant pool and a reference to the string constant pool is returned.
  3. For variable b, when assigning ab, first find out whether the same string exists from the string constant pool. If so, return the string reference.
  4. Therefore, a and b point to the same reference, so a==b holds.

Problem summary

It will take some time to have a more in-depth and comprehensive understanding of the constant pool.

For example, by reading the above, you think you have a very deep understanding of string constant pool. Yes, let's look at another problem:

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, as shown in the following figure. Obviously, str and str1 point to different reference addresses.

But let's transform 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 result 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 splicing through + is equivalent to taking out the String constant HelloWorld pointed to by the original String variable and adding the String constant pointed to by another String variable!, Regenerate into a new object.

Assuming that we splice String variables through the for loop, a large number of objects will be generated. If these objects are not recycled in time, it will cause a great waste of memory.

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

To prove what I said, 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 can be seen from the bytecode, a StringBuilder is built,

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

The string constants are then spliced by the append method, and finally the toString() method is used 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 is true.

There are many variants based on this problem. For example, change it again. What is the running 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 pointing to different address references, it is naturally 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);
}

Conclusion: only when you clearly understand all the knowledge points related to the string constant pool, you can answer accurately no matter how they change during the interview. This is the power of knowledge!
Copyright notice: unless otherwise stated, all articles on this blog adopt CC BY-NC-SA 4.0 license agreement. Reprint please indicate from Mic to take you to learn architecture!
If this article is helpful to you, please pay attention and praise. Your persistence is the driving force of my continuous creation. Welcome to WeChat public official account for more dry cargo.

Tags: Java

Posted on Mon, 08 Nov 2021 17:35:37 -0500 by matt_4013