Search code examples
javastringgarbage-collectionstringbuilder

Is the argument of append in StringBuilder a new String object and will it be garbage collected?


In the following code, When it reaches the comment, if the GC is not run, approximately 1000 object is created(according to OCA book), the StringBuilder is modified and remains as one object, the empty string " " is pooled and re-used, that's all that is explained. isn't the argument s a new String("s") that needs to be GCed, and i , will it not be converted to a new String object first, then combined with " " creates another new String object making them 2 String objects at that line, eligible for GC along with the append's argument, a total of 3 String object in every loop. so a sum of 3000 object when the code reaches the comment line?

public class Mounds {

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();
        String s = new String();
        for (int i = 0; i < 1000; i++) {
            s = " " + i;
            sb.append(s);
        }
// done with loop
    }
}

Solution

  • If we compile this code and look at the generated bytecode we can examine that exactly

      public static void main(java.lang.String[]) throws java.io.IOException;
    Code:
       0: new           #19                 // class java/lang/StringBuilder
       3: dup
       4: invokespecial #21                 // Method java/lang/StringBuilder."<init>":()V
       7: astore_1
       8: new           #22                 // class java/lang/String
      11: dup
      12: invokespecial #24                 // Method java/lang/String."<init>":()V
      15: astore_2
      16: iconst_0
      17: istore_3
      18: goto          47
      21: new           #19                 // class java/lang/StringBuilder
      24: dup
      25: ldc           #25                 // String
      27: invokespecial #27                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
      30: iload_3
      31: invokevirtual #30                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      34: invokevirtual #34                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      37: astore_2
      38: aload_1
      39: aload_2
      40: invokevirtual #38                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      43: pop
      44: iinc          3, 1
      47: iload_3
      48: sipush        1000
      51: if_icmplt     21
      54: return
    

    The instructions we care about are from 21 to 40. In 21, there is a second StringBuilder created, we will come back to that later.

    In 25 We see, that there is a ldc, which means that a literal is pushed to the stack, in this case it is the literal String " ".

    Then the real magic happens. The constructor of the second StringBuilder is called, which takes the literal from the stack as argument. Then the int i is loaded from the local variable array with iload_3, after that the append method of the second StringBuilder is called to append that i to it, and then the toString is called. With astore_2 and aload_1 the return value of the toString call is stored, and the first StringBuilder is loaded, and after that the String is loaded again. And finally the append method of the first StringBuilder is called to add that new String to the StringBuilder.

    So it turns out, that ther is a new StringBuilder created in every loop, because everytime you use " " + i a StringBuilder has to be created to concatenate the String and int. Additionally a new String will be created by the toString method of the intermediate StringBuilder, so there will be a total of 2000 Objects there.

    A better version would look like this:

    for (int i = 0; i < 1000; i++) {
            sb.append(' ');
            sb.append(i);
        }
    

    That will create the following bytecode:

      public static void main(java.lang.String[]) throws java.io.IOException;
    Code:
       0: new           #19                 // class java/lang/StringBuilder
       3: dup
       4: invokespecial #21                 // Method java/lang/StringBuilder."<init>":()V
       7: astore_1
       8: new           #22                 // class java/lang/String
      11: dup
      12: invokespecial #24                 // Method java/lang/String."<init>":()V
      15: astore_2
      16: iconst_0
      17: istore_3
      18: goto          37
      21: aload_1
      22: bipush        32
      24: invokevirtual #25                 // Method java/lang/StringBuilder.append:(C)Ljava/lang/StringBuilder;
      27: pop
      28: aload_1
      29: iload_3
      30: invokevirtual #29                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
      33: pop
      34: iinc          3, 1
      37: iload_3
      38: sipush        1000
      41: if_icmplt     21
      44: return
    

    We can see, that there now is only one StringBuilder, which gets its append method called twice, so no memory is allocated here and this should be better.