If Java strings are immutable and StringBuilder mutable, why do they lose the same amount of memory in my code?

I ran this code and I had some questions, this type got weird.

Using string:

while(true) { String s = String.valueOf(System.currentTimeMillis()); System.out.println(s); Thread.sleep(10); } 

Using StringBuilder:

 StringBuilder s = null; while(true) { s = new StringBuilder(); s.append(System.currentTimeInMillis()); System.out.println(s); Thread.sleep(10); } 

In both cases, they are stuck in 12,540 K of memory waste. Run this test on Windows XP SP2.

Why do they spend as much memory? Why did immutable String stop wasting memory? Off-topic: how can I convert a StringBuilder array to a byte array encoded in a specific encoding?

+4
source share
6 answers

It's hard to understand what you are actually asking here, but the application behaves exactly as I expected.

The rows are immutable and the garbage collector does not take them out. It's not him

Both mutable and immutable objects can be garbage collected in Java.

The actual criterion for determining whether an object is actually collecting garbage is reachability. Simply put, when the garbage collector finds out that the application can no longer use the object, the object will be deleted.

In both of your applications, objects of approximately the same size are created once every 10 milliseconds. At each iteration, a new object is created, and its link is assigned to s , replacing the previous link. This makes the previous object inaccessible and has the right to garbage collection. At some point, the Java virtual machine decides to run the garbage collector. This eliminates the whole unreachable object ... and the application continues.

I read that regular strings are not collected by the garbage collector, is that a lie?

This is not true for two counters:

  • The strings created by new String(...) , String.substring(...) , etc., are no different from any other Java object.

  • Lines that are interned (by calling String.intern() ) are stored in the row pool, which is stored in the PermGen heap. However, even the PermGen heap is garbage collection, albeit on a longer time scale, that the heap in which objects are usually created.

(Once upon a time, a PermGen heap was not garbage collected, but it was changed a long time ago.)

+6
source

You mix two different things:

  • Lines are immutable, but this has nothing to do with whether they are garbage collected. However, this means that if you need to make many changes to the string (for example, build a large string by adding one character at a time), then you end up making a lot of copies and a lot of work for the garbage collector.
  • String literals (i.e. strings written directly in the source code) are part of the interned string pool and are usually not garbage collected. However, this is done so that multiple instances of the same line in the source code are replaced with references to the same object, which can save a lot of space. And this is only possible because the lines are immutable, so the two parts of the program containing a link to the same line cannot interfere with each other.
+6
source
  • You seem to assume that a mutable class will spend more memory than a non-mutable class. I do not understand why.

  • Your code is incorrect if it is designed to allocate more memory in each loop. It simply assigns the s link to the new object, so the previous one is lost and will eventually be garbage.

  • To look at OS memory for the JVM, this is a very crude / inaccurate estimate of the allocated Java memory.

  • StringBuilder and String (and StringBuffer and char []) are efficient, they allocate about 2 bytes per char (Java uses some variation of UTF-16) plus a small (insignificant for large strings) overhead.

+4
source

Because you build and give up. You are not actually building any string using StringBuilder . Please note: you create a new StringBuilder object in each case.

+3
source

As already explained, since you are not mutating the string, you simply point s to the new value; old value must be garbage collected. Here is a snippet using stringBuffer to try to actually change the value of s that it points to.

 StringBuffer s = new StringBuffer(); while(true) { s.replace(0,13,Long.toString(System.currentTimeMillis())); System.out.println(s); Thread.sleep(10); } 

It should be noted that this does not solve the problem due to two things. First of all, we have to create a new line every time using Long.toString (), and secondly, since s.toString () is called; this will force a new line to pass the value of stringBuffer (at least that was the last time I checked). When we do s.replace, it will allocate a new array to hold this new string in order to preserve the immutability of the String.

Actually, in this trivial case, the best thing you can do (as far as I know):

 while(true) { System.out.println(Long.toString(System.currentTimeMillis())); Thread.sleep(10); } 
+2
source

I wanted to publish this as an answer to Stephen S, but for some reason I cannot; so here is the point of clarification ...

String.subString (...) DOES NOT create a new line. It refers to a point inside an existing string, and the return value of a substring is one of the reliable ways to inject memory leaks into your application (especially if you create a list of strings based on the substring values โ€‹โ€‹of another list of strings).

Best practice in this case:

 return new String(s.subString(...)); 
+2
source

All Articles