1

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
    }
}
  • Anyone has an idea why is it written that way in the loop, instead of `sb.append(" " + i)` ?? – Przemysław Moskal Mar 06 '18 at 21:38
  • @PrzemysławMoskal that does not change much... – luk2302 Mar 06 '18 at 21:40
  • 1
    its a test question, from a book –  Mar 06 '18 at 21:40
  • @luk2302 Doesn't it? As Strings are immutable, doesn't assigning new String to `s` cause creating new String in each loop, that can be garbage collected when there's a need to. On the other hand, using `StringBuilder.append()` doesn't create new String instance in each loop AFAIK. Please correct me if I'm wrong. – Przemysław Moskal Mar 06 '18 at 21:44
  • @PrzemysławMoskal you are kind of wrong since the argument to `append` is still a string that is created newly in each iteration, it does not matter if you save it in a local variable in between, – luk2302 Mar 06 '18 at 21:45
  • 1
    A new `String` isn't created to pass an argument to `append`. I don't know where you got that idea. `s` stores a reference value. Java is pass-by-value, so a copy of that reference value is passed, it refers to the same `String` object. – Sotirios Delimanolis Mar 06 '18 at 21:50
  • @luk2302 Thank you for giving the explanation. Seems like I need to do more research to see the real advantages of using StringBuilder, as for now it seems like I was completely wrong. – Przemysław Moskal Mar 06 '18 at 21:51
  • @PrzemysławMoskal the advantage for example would be when just doing `append(i)` vs. stitching the string together via `+=` by hand. – luk2302 Mar 06 '18 at 21:53
  • https://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.18.1 contains information regarding the `+` for Strings. – luk2302 Mar 06 '18 at 21:56
  • @Sotirios Delimanolis I thought since Strings are immutable passing a reference would violate that so a copy is passed, and a new String object is created then –  Mar 06 '18 at 21:59
  • That doesn't make any sense. If the object is immutable, there's no reason to make a copy. – Sotirios Delimanolis Mar 06 '18 at 22:01

3 Answers3

4

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.

Alexander Daum
  • 741
  • 4
  • 14
  • *"there now is only one StringBuilder"* ... and no intermediary Strings getting created and passed around, right? – luk2302 Mar 06 '18 at 22:06
  • @luk2302 exactly, because there is also no toString call – Alexander Daum Mar 06 '18 at 22:07
  • 1
    you mean not even a single new String object is created in that loop? I'm really confused, look this is the explained answer from the book Oracle OCA/OCP Java SE Programmer I & II Study Guide, StringBuilders are mutable, so all of the append() invocations are acting upon the same StringBuilder object over and over. Strings, however, are immutable, so every String concatenation operation results in a new String object. Also, the string " " is created once and reused in every loop iteration. –  Mar 06 '18 at 22:15
  • 1
    You are right, that StringBuilder is mutable, and the one you use explicitly is not discarded or recreated. But you have to understand, that String concatenation works by creating a new StringBuilder, appending everything to that and then call toString on it. Inside the toString call there will be a new String created, and since that changes everytime, it cannot be reused. – Alexander Daum Mar 06 '18 at 22:19
  • …and now compare with the bytecode generated with Java 9. – Holger Mar 07 '18 at 13:07
1

The optimal usage would be:

    StringBuilder sb = new StringBuilder(4000);
    for (int i = 0; i < 1000; ++i) {
        sb.append(' ').append(i);
    }
    ... do something with sb.toString()

As:

  • String s = new String(); creates an unnecessary empty string. Same as String s = "";. (Optimization not considered.)
  • s = " " + i; concatenates two strings into a new String. A task one should leave to the StringBuilder, as that is the sole purpose of it.
  • Appending a char ' ' is more efficient than a String " ".
  • new StringBuilder(4000) with an initial capacity one could use here, preventing intermittent reallocation on appending. 1000 numbers of which 900 are 3 digits, plus a space, will fit in 4000 chars.
Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
-1

the compiler probably realises that the scope of variable s is inside the loop so it inlines the assignment into the append() to produce

sb.append(" " + i)

so now only the conversion of the int creates a new String in each iteration.

Sharon Ben Asher
  • 13,849
  • 5
  • 33
  • 47
  • 1
    And the concatenation with the empty string creates another!? (not a downvoter) – luk2302 Mar 06 '18 at 21:46
  • not necessarily, might be more compiler magic since the outcome of the concatanation is known even before conversion from int – Sharon Ben Asher Mar 06 '18 at 21:48
  • The compiler does no such thing. Also this would have the exact same behavior as the original snippet. You have to copy the reference to the `String` created by the concatenation. – Sotirios Delimanolis Mar 06 '18 at 21:49
  • 1
    yes that's pretty much my point, where is the overall concatenation object? also passing the String argument must have created another object too, isn't it? –  Mar 06 '18 at 21:49
  • @SamuelIronhead no, not another object, just one more reference to the same existing object. – luk2302 Mar 06 '18 at 21:51
  • Interestingly, this creates different bytecode than doing it on its own line, as the intermediate String is not stored, the StringBuilder does not have to be reloaded onto the stack. But I am quite sure the JIT would get rid of that anyways, so it doesn't matter. – Alexander Daum Mar 06 '18 at 22:13