25

Consider a sample piece of code.

public void testString()
{     
    int i = 0;
    while(i < 100000000)
    {
        String s ="Hi hello bye" +i;
        i++;          
    }
}

In each iteration, a new String is created and its value is no longer needed for the next iteration. I tried printing the memory consumed pre and post this testString() functionality. Here are their values.

Before invoking testString():

Total Memory: 91684864 (87.4375 MB)
Max Memory:   1360855040 (1297.8125 MB)
Free Memory:  72163552 (68.82052612304688 MB)

After invoking testString():
Total Memory: 424280064 (404.625 MB)
Max Memory:   1360855040 (1297.8125 MB)
Free Memory:  171766816 (163.80960083007812 MB).

I see a large amount of memory being used and am afraid JVM Heap may go out of bounds due to the current way of handling Strings. The String generated for iteration 1 is no longer needed in iteration 2 and its storage space can be freed. I believe that is not happening here.

I tried using StringBuffer and StringBuilder objects and there seems a very marginal improvement in the memory usage.

Kindly assist me a better and optimal approach.

jmj
  • 237,923
  • 42
  • 401
  • 438
Vignesh
  • 259
  • 4
  • 15
  • 26
    Java won't garbage-collect until it _needs_ to. It won't garbage collect every string you generate immediately. Your testing methodology isn't actually giving you the information you need. – Louis Wasserman Apr 30 '14 at 05:37
  • 4
    Compiler will use `StringBuilder` by default if you are appending the String . So no need to use it explicitly and [this](http://stackoverflow.com/questions/231051/is-there-a-memory-efficient-replacement-of-java-lang-string) must help you – Santhosh Apr 30 '14 at 05:39
  • after the work done for string s you can assign it to null – ashishmaurya Apr 30 '14 at 05:44
  • 3
    @user3091574: That won't have any benefit at all. – Jon Skeet Apr 30 '14 at 05:46
  • If that so Why dont you call garbage collector . whenever you needed ... I mean after itreation – Rookie007 Apr 30 '14 at 05:55
  • @looser: There is no guarantee gc happens as on request. Compiler may ignore the request. – Vignesh Apr 30 '14 at 06:41
  • @Vignesh but it may honor it, it fully depends on the implementation but don't diss it when it might be exactly what you need. – ratchet freak Apr 30 '14 at 09:29
  • 1
    I think the key words in your question are that you are "afraid [the] JVM Heap may go out of bounds". I don't believe the JVM can throw OutOfMemory without first running GC, so this should be impossible with the example you gave. Or have you actually run out of memory? – Dan Getz Apr 30 '14 at 10:59
  • If you are running out of memory, you could run your application with the `-XX:+HeapDumpOnOutOfMemoryError` flag. Then, if your application runs out of memory, it will dump the heap. You can examine the heap dump in VisualVM to find out what is using so much memory. – James_pic Apr 30 '14 at 12:51
  • Whether `System.gc()` actually does anything is basically irrelevant here. Memory usage is going to spike anyway, and if/when it becomes an issue, the JVM will already run a GC pass automatically. The only thing `System.gc()` *might* do to help is move the inevitable GC delay to a less arbitrary time. But even that will only do so much. – cHao Apr 30 '14 at 16:37
  • possible duplicate of [Why does java wait so long to run the garbage collector?](http://stackoverflow.com/questions/7114661/why-does-java-wait-so-long-to-run-the-garbage-collector) – Raedwald May 02 '14 at 07:20
  • 1
    Possible duplicate of http://stackoverflow.com/questions/17646509/why-do-garbage-collectors-wait-before-deallocating – Raedwald May 02 '14 at 07:23
  • See also http://stackoverflow.com/questions/599161/best-way-to-convert-an-arraylist-to-a-string – Raedwald May 02 '14 at 07:33

7 Answers7

51

The String generated for iteration 1 is no longer needed in iteration 2 and its storage space can be freed. I believe that is not happening here.

It definitely is happening.

You're creating 100 million strings, each of which is at least 13 characters - and most of which will be about 20 characters. Each string consists of an object (which has overhead) and a char[] - so I'd guess at it taking around 60 bytes for a 20-character string.

If garbage collection weren't being effective, 100 million objects requiring 60 bytes each would require 6GB - whereas you're seeing a total memory which is only about 300MB larger than it was to start with.

The strings are being collected - just not immediately.

You haven't told us what you need to do with the strings in your real code (I'm assuming there's a real motivation for this) - assuming you actually need a string in each iteration of a loop, I don't think using StringBuilder is going to help you. If you only need the data is a StringBuilder then you can make it a lot more efficient, but it's rare that you create a StringBuilder but don't call toString on it.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Thank you for the heads up. You have really convinced me that some cleaning do happen. :) Yes String in each iteration is indeed needed. However the actual string is a 3 table join query and the String size is more than the example provided. Is there any approach to determine when JVM clears the garbage?(As in, after how many records being processed) – Vignesh Apr 30 '14 at 07:25
  • 2
    @Vignesh there are JVM options to log the GC operations – ratchet freak Apr 30 '14 at 09:38
  • @Jon Skeet, it is too late, but why are we multiplying here? the 1st one will be in pool, and then only 4 byte int.. the only i part of it needs to be allocated .. no? – Sendi_t Dec 22 '21 at 17:38
  • @Sendi_t: No, that's not how strings work. Each string contains the complete text. (At least, that used to be how they worked... it's possible that the internals have changed now...) – Jon Skeet Dec 22 '21 at 17:40
7

What will happen on the first run

JVM runs the code, generates the strings, and at certain intervals the Garbage Collector frees the used memory. Beside some wasted execution time, the program will run normally.

What will happen if the function is called frequently

JVM will begin to optimize the loop, realize that nothing is done with those strings ever and marks the entire function as dead code. Eventually calling the function will do literally nothing, as the JVM converted the content to a simple return

Kindly assist me a better and optimal approach.

Since even the JVM has no clue what your code is supposed to do... what is it what you want to do? There might be an optimal solution for your actual problem at hand, that is very different from the code sample that you posted initially.

TwoThe
  • 13,879
  • 6
  • 30
  • 54
  • :Thanks a lot for your valuable info. Yes, the code presented above may not represent the ideal picture. All strings put inside the loop are Select Queries that needs to be dynamically generated on each iteration. Note: The String generated on each iteration is different. I had a similar implementation and JVM Crashed with an error report stating Heap goes out of Bounds. The intent behind this post is to catch where is the memory leak happening? If you say, JVM takes care of it, then it should not have ideally crashed. – Vignesh Apr 30 '14 at 11:57
  • 3
    Do not dynamically generate select queries. Ever. http://en.wikipedia.org/wiki/SQL_injection – James_pic Apr 30 '14 at 12:32
  • 1
    The JVM takes care of it when those strings are unused and unreferenced. Are they really unreferenced after you use them, or could the library you passed them to be holding on to them (or something related to them)? – Dan Getz Apr 30 '14 at 13:23
  • If you have dynamic select queries where only one (or a few) value change each call, then you can go with stored procedures (aka PLSQL). In this case you only send one (same) string and the variables, and the database does the rest. Which will not only speed up Java but the DB queries as well. – TwoThe Apr 30 '14 at 14:45
  • 2
    @James_pic it's perfectly possibly to dynamically generate parameterized queries. – Blorgbeard May 01 '14 at 01:11
  • Thx @Gwyn, that was what I wanted to write. SO should stop me from writing answers past 3 am. ;) – TwoThe May 01 '14 at 10:09
  • @Blorgbeard Yes, I was oversimplifying my argument to make a point. I'm aware that it's possible to safely generate dynamic queries, and indeed have made exactly your argument in a different context (in a discussion with a software vendor whose software I was testing. They were trying to claim that it was inappropriate to use prepared statements because their code was dynamic, which as you point out, is incorrect). – James_pic May 01 '14 at 10:11
3

It depends on how you used StringBuilder. This

    StringBuilder sb = new StringBuilder("");
    while (i < 100000000) {
        sb.delete(0, sb.length());
        sb.append("Hi hello bye").append(i);
        i++;
    }

will be much more efficient both in memory consumption and speed

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
  • 4
    Yes, but it won't allow you to actually use the values as strings. If you need the strings within the loop (which is likely) then you're not going to get any benefit. If you *are* going to use code like that, why not just delete from the character after "bye"? No need to keep appending all the time... – Jon Skeet Apr 30 '14 at 05:44
  • @Evgeniy, Still I feel Strings stay in memory as unreferenced. Here is the memory info after running this piece of code. Total Memory: 382009344 (364.3125 MB) Max Memory: 1360855040 (1297.8125 MB) Free Memory: 264932400 (252.6592254638672 MB) Marginal improvement of around 30 MB less space. – Vignesh Apr 30 '14 at 07:31
3

String is immutable, when you going to add a string continuously which is create a string object for every iteration, so memory has been exceed. if you use StringBuilder, it works in single object even it handles more than one iteration is happen. StringBuilder is mutable.

StringBuilder iter = new StringBuilder("");
while (i < 100000000) 
{
    iter.delete(0, iter.length());
    iter.append("Hi hello bye").append(i);
    i++;
} 
Tassisto
  • 9,877
  • 28
  • 100
  • 157
learner
  • 3,092
  • 2
  • 21
  • 33
  • 1
    If you're not going to do anything with each sequence of chars, you might as well say `i = 100000000; StringBuilder iter = new StringBuilder("Hi hello bye" + (i - 1));` and be done. And if you are, in any sane case you're still creating an extra String. You're just not creating a new StringBuilder each time -- which reduces the garbage, but not to a point that avoids the spike in memory usage. – cHao Apr 30 '14 at 16:46
3

The JVM should never run out of heap memory from unreferenced objects, such as the strings in your example program, because before it throws an OutOfMemory exception, it will run garbage collection. See the semantics of this exception from the Java Virtual Machine Specification, section 6.3:

OutOfMemoryError: The Java Virtual Machine implementation has run out of either virtual or physical memory, and the automatic storage manager was unable to reclaim enough memory to satisfy an object creation request.

Dan Getz
  • 8,774
  • 6
  • 30
  • 64
  • 1
    should ***almost*** never... See for example http://stackoverflow.com/questions/13531004/java-outofmemoryerror-strange-behaviour – assylias May 17 '14 at 06:48
  • 1
    Well, I said "unreferenced" objects. In your example, when `OutOfMemory` was thrown, it was because references to the objects still existed in the stack frame of the current method. – Dan Getz May 17 '14 at 16:58
3

Usage of string builders is the best option you have. Since the number of strings/string builders required by you are huge in number, you can not expect JVM to virtually avoid using much of a memory in any case. Referring to your statistics above:

With use of string: % of free memory on total memory is 40.48% ((163.80960083007812 MB/404.625 MB )*100).

With use of string builders: % of free memory on total memory is 69.35 % ((252.659 MB/364.3125 MB)*100), which is quite a significant improvement. Also, usage of the above variable is only inside the scope of loop, therefore JVM's garbage collector will run to clear the memory once required.

Farid Nouri Neshat
  • 29,438
  • 6
  • 74
  • 115
Yasha
  • 161
  • 1
  • 4
  • 13
2

Since scope of variable is just for the iteration of while loop, so here you don't need to worry about memory overflow as on next Garbage Collector execution it will free all the memory:

while(i < 100000000)
{
    String s ="Hi hello bye" +i;
    i++;          
}// no more required the s afterward

In each iteration String s will create a new object and but previous one is not required now, so it is in memory until Garbage collector clean it.

Zaheer Ahmed
  • 28,160
  • 11
  • 74
  • 110