[Logo] Enterprise Client Community
  [Search] Search   [Recent Topics] Recent Topics   [Members]  Member Listing   [Groups] Back to home page 
[Register] Register / 
[Login] Login 
StringBuffer is dangerous when dealing with lonnnng Strings!!!  XML
Forum Index -> Development - Code Snippets
Author Message
CaptainCasa

Power User
[Avatar]

Joined: 21/11/2007 12:23:06
Messages: 5510
Offline

Hi,

today we were forced to dive into the StringBuffer class - after looking into a OutOfMemory problem in our client.

It's "amazing" that the following code causes an OutOfMemory - event though the virtual machine is started with -Xmx256m:

Code:
     public static void main(String[] args)
     {
         try
         {
             char[] chars = new char[30000000];
             for (int i=0; i<chars.length; i++) chars[i] = 'A';
             String s = new String(chars);
             StringBuffer sb = new StringBuffer();
             System.out.println("Now appending s");
             sb.append(s);
             System.out.println("Now appending Hallo");
             sb.append("Hallo!"); 
             // ----
             System.out.println("Finished!");
         }
         catch (Throwable t)
         {
             t.printStackTrace();
         }
     }
 
 
 OUTPUT:
 Now appending s
 Now appending Hallo
 java.lang.OutOfMemoryError: Java heap space
 	at java.util.Arrays.copyOf(Arrays.java:2882)
 	at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
 	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:390)
 	at java.lang.StringBuffer.append(StringBuffer.java:224)
 	at org.eclnt.client.ztest.TestMem.main(TestMem.java:22)
 
 


A String with the length of 30.000.000 characters is added to a stringbuffer - that's it. It's even more astonishing that the error is NOT thrown when appending the "big" string, but when appending "Hallo".

The reason is the internal processing of StirngBuffer. StringBuffer keeps everyhing in an char-array, and there is a certain method that increases the char-array-size when finding out that the array does not fit anymore. The method increases the size by at least "2 * currentSize + 2". This may make sense for small sizes, but it's completely a wrong decision for big sizes. In our case the appending of "Hello" means that the total array size is increased by 60.000.002 as minimum, so that the minimum size is 90.000.002 afterwards. And now, the previous char array with length of 30.000.000 needs to be copied into the one of 90.000.002 - this means both arrays co-exist for a short duration of time, resulting in 120.000.002 characters being in memory (and of course there is some additional memory blocked by the array object). So the appending of "Hallo" means a lot... - enough to cause out of memory.

When concatenating long strings, then it is as consequence essential to carefully work with StringBuffers. We eliminated our OutOfMemory problems by using the following utitlity method:

Code:
     public static String concatenateStrings(List<String> items)
     {
         if (items == null)
             return null;
         if (items.size() == 0)
             return "";
         int expectedSize = 0;
         for (String item: items)
             expectedSize += item.length();
         StringBuffer result = new StringBuffer(expectedSize);
         for (String item: items)
             result.append(item);
         return result.toString();
     }
 


When now changing the test code to:
Code:
     public static void main(String[] args)
     {
         try
         {
             char[] chars = new char[30000000];
             for (int i=0; i<chars.length; i++) chars[i] = 'A';
             String s = new String(chars);
             List<String> buffer = new ArrayList<String>();
             System.out.println("Now appending s");
             buffer.add(s);
             System.out.println("Now appending Hallo");
             buffer.add("Hallo!");
             String all = concatenateStrings(buffer);
             // ----
             System.out.println("Finished!");
         }
         catch (Throwable t)
         {
             t.printStackTrace();
         }
 


...then no error occurs anymore.

Regards,
Björn

Björn Müller, CaptainCasa GmbH
krinninger

Power User

Joined: 17/11/2008 21:19:41
Messages: 567
Offline

you can set an inital capacity for StringBuffer i guess.

Also i would recommend to use StringBuilder instead of StringBuffer (in case you don't need a garanteed synchronisation).

this test (link) resulted in a 34% better perfomance for StringBuilder.

http://littletutorials.com/2008/07/16/stringbuffer-vs-stringbuilder-performance-comparison/
CaptainCasa

Power User
[Avatar]

Joined: 21/11/2007 12:23:06
Messages: 5510
Offline

Hi,

you are 100% right...!
The setting of an internal capacity is by the way the "trick" behind the concatenateStrings-method int the text above. Before appending things in the StringBuffer, its size is calculated (variable "expectedSize").

Regards, Björn


Björn Müller, CaptainCasa GmbH
unger

Power User

Joined: 22/07/2008 05:19:28
Messages: 261
Offline

Hello Björn,

so what can I do now, when I get Java Heap Space Exception while uploading documents with FILEUPLOADBUTTON?

Have we to update Enterprise Client or is there a work around?

Regards,

Joachim
unger

Power User

Joined: 22/07/2008 05:19:28
Messages: 261
Offline

Have a look at the hardcopy:
380 MByte Heap while uploading 6 MByte document??
 Filename peak400MUpload6M.pdf [Disk] Download
 Description
 Filesize 99 Kbytes
 Downloaded:  1478 time(s)

CaptainCasa

Power User
[Avatar]

Joined: 21/11/2007 12:23:06
Messages: 5510
Offline

Hi,

well, a 300 pages document is passed to the client, and there it is visualized by a Java PDF component - so the MB-size of the document is just one issue of the whole scenario. As long as the memory is back to normal after closing the document, I am not in alarm mode - also knowing that the PDFRENDERER is used in "some pages scenario" typically.

Regards, Björn

Björn Müller, CaptainCasa GmbH
 
Forum Index -> Development - Code Snippets
Go to:   
Powered by JForum 2.1.6 © JForum Team