United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
JDK-4203912 : JTextArea uses too much memory (if large byte array passed to cto=

Details
Type:
Bug
Submit Date:
1999-01-19
Status:
Resolved
Updated Date:
2013-11-01
Project Name:
JDK
Resolved Date:
2003-11-03
Component:
client-libs
OS:
generic,windows_nt,windows_2000
Sub-Component:
javax.swing
CPU:
generic,x86
Priority:
P4
Resolution:
Fixed
Affected Versions:
1.2.0,1.2.2,1.3.0,1.4.2
Fixed Versions:
5.0 (b28)

Related Reports
Duplicate:
Relates:
Relates:

Sub Tasks

Description

Name: krT82822			Date: 01/18/99

=20
While writing a simple file viewing class, I
encountered a problem regarding the necessary
memory. For example, if I do something like ...

//---------------------------------------------
// 'sb' is a byte array with some text
//      read from a file
//
    JTextArea ta=3Dnew JTextArea(new String(sb));=20
//
// OR !
//
    JTextArea ta=3Dnew JTextArea();
    ta.setText(new String(sb));
//
//---------------------------------------------

the amount of memory needed is incredible.

Let=B4s say, the size of 'sb' is 2 MB, then the
memory allocated for the JTextArea instance is
something like 25,8 MB. This memory usage is
almost linear, a text twice the size (4 MB)
causes 51,4 MB to be allocated.=20

The same happens, if I use a JEditorPane instead
of a JTextArea.

I consider this to be a bug, because this amount
of memory is just too much to be reasonable,
considering that only plain text is displayed.

I experience this behaviour in JDK 1.1.5 with
Swing 1.0.2, JDK 1.1.7 and 1.2RC1.
(Review ID: 43400)
======================================================================

Name: skT88420			Date: 05/27/99


Swing Text Components(JTextArea, JTextField...) seem to leak 
memory as measured by WindowsNT Task Manager Memory Usage 
History when given focus.  Leak does not occur when focus is lost
or when their AWT counterparts are used, as demonstrated in the
following code:

import java.awt.*;
import javax.swing.*;

public class SwingTextMemoryTest extends JFrame{

	private JPanel panel;
	private JTextField jTextField;
	private TextField textField;
	
	public SwingTextMemoryTest()
		{
		 super();
		 panel = new JPanel();
                 jTextField = new JTextField("JTextField",15);
		 textField = new TextField("AWT TextField",15);
		 panel.add(textField); // Stable with focus
		 panel.add(jTextField); // Leaks when given focus
		 this.getContentPane().add(panel);
		 setSize(500,500);
		 setVisible(true);
		}
	
	public static void main(String[] args)
		{ SwingTextMemoryTest test = new SwingTextMemoryTest(); }

}

Thank you for your attention.

- Mark Horwath
(Review ID: 83582)
======================================================================

Name: tb29552			Date: 12/24/99


java version "1.2.2"
Classic VM (build JDK-1.2.2-W, native threads, symcjit)


When calling setText() on a JTextArea, * with a huge string as the argument * (
say 2.6 MB ), the memory usage shoots up by atleast 30-35 MB.

The code I enclosed :-   There is a JFrame, with a JPanel containing a
JTextArea ( enclosed in a JScrollPane ) as the contentpane.  A file name is
specified as the command line argument.  The program opens the specified file,
reads its contents, and calls setText() on the JTextArea.  When the input file
is small ( say around 1 MB ), there is no problem, but when I gave a input file
of size 2.6 MB, the memory usage went up to 45 MB.  This happened on JDK1.2.2
both in Windows NT and Solaris.  ( I have 128 MB RAM in my machine ).


Code: ////////////////////////////////////////////////////////////////////////

import java.awt.* ;
import java.awt.event.* ;

import javax.swing.* ;
import javax.swing.event.* ;

import java.io.* ;

class JTextAreaTest  {
	JFrame f = new JFrame("Test") ;
	JTextArea ta = new JTextArea(30,30) ;
	JPanel mainPanel = new JPanel() ;
	String m_fileName ;
	StringBuffer contents = new StringBuffer() ;
	
	JTextAreaTest(String fileName)   {
		m_fileName = fileName ;
		setupTextArea() ;
		JScrollPane tempPanel = new JScrollPane(ta) ;
		f.getContentPane().add(tempPanel);

		f.addWindowListener(new WindowAdapter()   {
			public void windowClosing(WindowEvent e)  {
				f.dispose() ;
			}
		});
		
		f.pack() ;
		f.show() ;
	}


	private void setupTextArea()   {
		BufferedReader in = null ;
		try  {
			in  =  new BufferedReader(new FileReader(m_fileName));
		}
		catch (FileNotFoundException e)  {
			e.printStackTrace() ;
			return ;
		}

		String curLine = null ;

		while (true)  {
			try  {
				curLine = in.readLine() ;
	  
			}
			catch (IOException e)  {
				e.printStackTrace() ;
			}

			if (curLine == null)  {
				break ;
			}
			contents.append(curLine);
			contents.append("\n");
		}
		try {
			in.close();
		}
		catch (IOException e)  {
		  e.printStackTrace() ;
		}

		ta.setText(contents.toString());
	}

	public static void main(String[] args)    {
		if ( args.length != 1)  {
			System.out.println("Usage: java JTextAreaTest
<fileName>" );
			System.exit(1);
		}
		JTextAreaTest taTest = new JTextAreaTest(args[0]) ;
	}
}

/////////////////////////////////////////////////////////////////////////
(Review ID: 99331)
======================================================================

                                    

Comments
SUGGESTED FIX



Name: anR10225			Date: 09/18/2003


*** /net/crown/export1/naa/tiger//webrev/4203912/src/share/classes/javax/swing/text/AbstractDocument.java-	Tue Sep  2 19:09:50 2003
--- AbstractDocument.java	Tue Sep  2 18:21:22 2003
***************
*** 1202,1210 ****
              
              // Create a Bidi over this paragraph then get the level
              // array.
!             String pText;
              try {
!                 pText = getText(pStart, pEnd-pStart);
              } catch (BadLocationException e ) {
                  throw new Error("Internal error: " + e.toString());
              }
--- 1202,1210 ----
              
              // Create a Bidi over this paragraph then get the level
              // array.
!             Segment seg = SegmentCache.getSharedSegment();
              try {
!                 getText(pStart, pEnd-pStart, seg);
              } catch (BadLocationException e ) {
                  throw new Error("Internal error: " + e.toString());
              }
***************
*** 1218,1228 ****
  		    bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
  		}
  	    }
! 	    bidiAnalyzer = new Bidi(pText, bidiflag);
  	    BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
  	    levelsEnd += bidiAnalyzer.getLength();
  
              o =  p.getEndOffset();
          }
  
          // REMIND(bcb) remove this code when debugging is done.
--- 1218,1230 ----
  		    bidiflag = Bidi.DIRECTION_RIGHT_TO_LEFT;
  		}
  	    }
! 	    bidiAnalyzer = new Bidi(seg.array, seg.offset, null, 0, seg.count, 
!                     bidiflag);
  	    BidiUtils.getLevels(bidiAnalyzer, levels, levelsEnd);
  	    levelsEnd += bidiAnalyzer.getLength();
  
              o =  p.getEndOffset();
+             SegmentCache.releaseSharedSegment(seg);
          }
  
          // REMIND(bcb) remove this code when debugging is done.
*** /net/crown/export1/naa/tiger//webrev/4203912/src/share/classes/javax/swing/text/GapContent.java-	?? ??? 29 19:39:04 2003
--- GapContent.java	?? ??? 29 19:39:28 2003
***************
*** 336,341 ****
--- 336,354 ----
      }
  
      /**
+      * Overridden to make growth policy less agressive for large
+      * text amount.
+      */
+     int getNewArraySize(int reqSize) {
+         if (reqSize < GROWTH_SIZE) {
+             return super.getNewArraySize(reqSize);
+         } else {
+             return reqSize + GROWTH_SIZE;
+         }
+     }
+     final static int GROWTH_SIZE = 1024 * 512;
+ 
+     /**
       * Move the start of the gap to a new location,
       * without changing the size of the gap.  This 
       * moves the data in the array and updates the
*** /net/crown/export1/naa/tiger//webrev/4203912/src/share/classes/javax/swing/text/GapVector.java-	?? ??? 29 19:39:03 2003
--- GapVector.java	?? ??? 29 19:20:30 2003
***************
*** 207,213 ****
  	int oldSize = getArrayLength();
  	int oldGapEnd = g1;
  	int upperSize = oldSize - oldGapEnd;
! 	int arrayLength = (newSize + 1) * 2;
  	int newGapEnd = arrayLength - upperSize;
  	resize(arrayLength);
  	g1 = newGapEnd;
--- 207,213 ----
  	int oldSize = getArrayLength();
  	int oldGapEnd = g1;
  	int upperSize = oldSize - oldGapEnd;
! 	int arrayLength = getNewArraySize(newSize);
  	int newGapEnd = arrayLength - upperSize;
  	resize(arrayLength);
  	g1 = newGapEnd;
***************
*** 218,223 ****
--- 218,233 ----
  	}
      }
  
+     /**
+      * Calculates a new size of the storage array depending on required
+      * capacity.
+      * @param reqSize the size which is necessary for new content
+      * @return the new size of the storage array
+      */
+     int getNewArraySize(int reqSize) {
+         return (reqSize + 1) * 2;
+     }
+ 
      /**
       * Move the start of the gap to a new location,
       * without changing the size of the gap.  This 
*** /net/crown/export1/naa/tiger//webrev/4203912/src/share/classes/javax/swing/text/PlainDocument.java-	?? ??? 29 19:39:04 2003
--- PlainDocument.java	?? ??? 29 19:17:08 2003
***************
*** 172,181 ****
  	int rmOffs1 = rmCandidate.getEndOffset();
  	int lastOffset = rmOffs0;
  	try {
! 	    String str = getText(offset, length);
  	    boolean hasBreaks = false;
  	    for (int i = 0; i < length; i++) {
! 		char c = str.charAt(i);
  		if (c == '\n') {
  		    int breakOffset = offset + i + 1;
  		    added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset));
--- 172,181 ----
  	int rmOffs1 = rmCandidate.getEndOffset();
  	int lastOffset = rmOffs0;
  	try {
!             getContent().getChars(offset, length, s);
  	    boolean hasBreaks = false;
  	    for (int i = 0; i < length; i++) {
!                 char c = s.array[s.offset + i];
  		if (c == '\n') {
  		    int breakOffset = offset + i + 1;
  		    added.addElement(createLeafElement(lineMap, null, lastOffset, breakOffset));
***************
*** 213,219 ****
  	}
  	super.insertUpdate(chng, attr);
      }
!     
      /**
       * Updates any document structure as a result of text removal.
       * This will happen within a write lock. Since the structure
--- 213,219 ----
  	}
  	super.insertUpdate(chng, attr);
      }
! 
      /**
       * Updates any document structure as a result of text removal.
       * This will happen within a write lock. Since the structure
***************
*** 300,303 ****
--- 300,304 ----
      private AbstractElement defaultRoot;
      private Vector added = new Vector();     // Vector<Element>
      private Vector removed = new Vector();   // Vector<Element>
+     private Segment s = new Segment();
  }
*** /net/crown/export1/naa/tiger//webrev/4203912/src/share/classes/javax/swing/text/Utilities.java-	Tue Sep  2 19:09:51 2003
--- Utilities.java	Tue Sep  2 19:03:30 2003
***************
*** 386,402 ****
  	int lineStart = line.getStartOffset();
  	int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  	
! 	String s = doc.getText(lineStart, lineEnd - lineStart);
! 	if(s != null && s.length() > 0) {
! 	    BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	    words.setText(s);
! 	    int wordPosition = offs - lineStart;
! 	    if(wordPosition >= words.last()) {
! 		wordPosition = words.last() - 1;
! 	    } 
! 	    words.following(wordPosition);
! 	    offs = lineStart + words.previous();
! 	}
  	return offs;
      }
  
--- 386,404 ----
  	int lineStart = line.getStartOffset();
  	int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  	
!         Segment seg = SegmentCache.getSharedSegment();
!         doc.getText(lineStart, lineEnd - lineStart, seg);
!         if(seg.count > 0) {
!             BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
!             words.setText(seg);
!             int wordPosition = offs - lineStart;
!             if(wordPosition >= words.last()) {
!                 wordPosition = words.last() - 1;
!             } 
!             words.following(wordPosition);
!             offs = lineStart + words.previous();
!         }
!         SegmentCache.releaseSharedSegment(seg);
  	return offs;
      }
  
***************
*** 418,427 ****
  	int lineStart = line.getStartOffset();
  	int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  	
! 	String s = doc.getText(lineStart, lineEnd - lineStart);
! 	if(s != null && s.length() > 0) {
              BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	    words.setText(s);
  	    int wordPosition = offs - lineStart;
  	    if(wordPosition >= words.last()) {
  		wordPosition = words.last() - 1;
--- 420,429 ----
  	int lineStart = line.getStartOffset();
  	int lineEnd = Math.min(line.getEndOffset(), doc.getLength());
  	
!         Segment seg = SegmentCache.getSharedSegment();
!         if(seg.count > 0) {
              BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	    words.setText(seg);
  	    int wordPosition = offs - lineStart;
  	    if(wordPosition >= words.last()) {
  		wordPosition = words.last() - 1;
***************
*** 428,433 ****
--- 430,436 ----
  	    } 
  	    offs = lineStart + words.following(wordPosition);
  	}
+         SegmentCache.releaseSharedSegment(seg);
  	return offs;
      }
  
***************
*** 471,487 ****
  	if ((offs >= lineEnd) || (offs < lineStart)) {
  	    throw new BadLocationException("No more words", offs);
  	}
! 	String s = doc.getText(lineStart, lineEnd - lineStart);
          BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	words.setText(s);
  	if ((first && (words.first() == (offs - lineStart))) &&	
! 	    (! Character.isWhitespace(s.charAt(words.first())))) {
  
  	    return offs;
  	}
! 	int wordPosition = words.following(offs - lineStart);
  	if ((wordPosition == BreakIterator.DONE) || 
! 	    (wordPosition >= s.length())) {
  		// there are no more words on this line.
  		return BreakIterator.DONE;
  	}
--- 474,491 ----
  	if ((offs >= lineEnd) || (offs < lineStart)) {
  	    throw new BadLocationException("No more words", offs);
  	}
!         Segment seg = SegmentCache.getSharedSegment();
!         doc.getText(lineStart, lineEnd - lineStart, seg);
          BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	words.setText(seg);
  	if ((first && (words.first() == (offs - lineStart))) &&	
! 	    (! Character.isWhitespace(seg.array[seg.offset + words.first()]))) {
  
  	    return offs;
  	}
! 	int wordPosition = words.following(seg.offset + offs - lineStart);
  	if ((wordPosition == BreakIterator.DONE) || 
! 	    (wordPosition >= seg.count)) {
  		// there are no more words on this line.
  		return BreakIterator.DONE;
  	}
***************
*** 488,494 ****
  	// if we haven't shot past the end... check to 
  	// see if the current boundary represents whitespace.
  	// if so, we need to try again
! 	char ch = s.charAt(wordPosition);
  	if (! Character.isWhitespace(ch)) {
  	    return lineStart + wordPosition;
  	}
--- 492,498 ----
  	// if we haven't shot past the end... check to 
  	// see if the current boundary represents whitespace.
  	// if so, we need to try again
! 	char ch = seg.array[seg.offset + wordPosition];
  	if (! Character.isWhitespace(ch)) {
  	    return lineStart + wordPosition;
  	}
***************
*** 503,508 ****
--- 507,513 ----
  		return offs;
  	    }
  	}
+         SegmentCache.releaseSharedSegment(seg);
  	return BreakIterator.DONE;
      }
  
***************
*** 547,556 ****
  	if ((offs > lineEnd) || (offs < lineStart)) {
  	    throw new BadLocationException("No more words", offs);
  	}
! 	String s = doc.getText(lineStart, lineEnd - lineStart);
          BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	words.setText(s);
! 	if (words.following(offs - lineStart) == BreakIterator.DONE) {
  	    words.last();
  	}
  	int wordPosition = words.previous();
--- 552,562 ----
  	if ((offs > lineEnd) || (offs < lineStart)) {
  	    throw new BadLocationException("No more words", offs);
  	}
!         Segment seg = SegmentCache.getSharedSegment();
! 	doc.getText(lineStart, lineEnd - lineStart, seg);
          BreakIterator words = BreakIterator.getWordInstance(c.getLocale());
! 	words.setText(seg);
! 	if (words.following(seg.offset + offs - lineStart) == BreakIterator.DONE) {
  	    words.last();
  	}
  	int wordPosition = words.previous();
***************
*** 565,571 ****
  	// if we haven't shot past the end... check to 
  	// see if the current boundary represents whitespace.
  	// if so, we need to try again
! 	char ch = s.charAt(wordPosition);
  	if (! Character.isWhitespace(ch)) {
  	    return lineStart + wordPosition;
  	}
--- 571,577 ----
  	// if we haven't shot past the end... check to 
  	// see if the current boundary represents whitespace.
  	// if so, we need to try again
! 	char ch = seg.array[seg.offset + wordPosition];
  	if (! Character.isWhitespace(ch)) {
  	    return lineStart + wordPosition;
  	}
***************
*** 577,582 ****
--- 583,589 ----
  	if (wordPosition != BreakIterator.DONE) {
  	    return lineStart + wordPosition;
  	}
+         SegmentCache.releaseSharedSegment(seg);
  	return BreakIterator.DONE;
      }
  

###@###.### 

======================================================================
                                     
2004-08-20
CONVERTED DATA

BugTraq+ Release Management Values

COMMIT TO FIX:
tiger-beta

FIXED IN:
tiger-beta

INTEGRATED IN:
tiger-b28
tiger-beta


                                     
2004-08-20
EVALUATION

SUMMARY: The huge kinds of wastage reported could not be reproduced under 1.2.1. I did however see an approximately 2x memory usage over what I expected or could easily explain away.

DETAILS:
This bug did not come with an executable code example.  I created my own test which is attached below. I have also attached an hprof dump that shows the memory allocations in detail. (I have not included my results as reported by the prgoram as they duplicate whats in the hprof and can be easily reonstructed by running the test under 1.2.1)

The results are that JTextArea appears to require 4 bytes per character.  2 bytes is understandable in that Java is unicode internally. The other 2 bytes I could not as easily dismiss so I did not retire this bug.

jeffrey.kesselman@Eng 1999-04-29

---------------------------------------------------
Looking at the output from -Xhprof seemed to indicate that it was the GapBuffer class which was allocating all the memory in question.  The sample program creates a String w/ 2 million characters.  You'd expect that to need 4Meg to store (unicode can be a pain), but instead the GapBuffer allocates a char[] which is 8Meg in size.
steve.wilson@eng 1999-04-29

GapBuffer will allocate up to twice the amount of needed storage when it
resizes to try to prevent excessive resize requests which are expensive.
For 2 meg bytes, the buffer will store 4 meg characters, and if twice the
size needed that would add up to 8meg.  Also managed by the GapBuffer is
the set of Position objects that make up the line map.  These are done
in a funcky way to get weak references on 1.1 in a platform independant
way.  These could be changed to use weak references in 1.2 and would
probably be cheaper.  Also, positions representing the same location could
be shared which would reduce the number of posistion objects since there
are typically at least two from the edges of two elements butting up to
each other.  The resize strategy could be less aggressive when getting
large and this would reduce the overall array.

-----
Fix for 4525843 improves memory consumption. 
we are using weak references now. Positions for the same location are shared.

On some simple test cases memory footprint is reduced by 50%

###@###.### 2002-06-20

Name: anR10225			Date: 07/29/2003


    Also for each line in PlainDocument the following data
structures are created  :
    . instance of javax.swing.text.AbstractDocument.LeafElement (32 bytes)
    . instance of javax.swing.text.GapContent.StickyPosition (48 bytes)
      (only one instance since Position sharing is used)
    . entry in array
    javax.swing.text.AbstractDocument.BranchElement.children (4 bytes)

    The intensive memory growth could also be visibly because of
storage array reallocating which causes intensive VM heap growth.
    The possible decision could be changing storage array resize
policy in GapContent class. For example it could be the same as
in GapVector ((requredSize + 1) * 2) when the size of text content
is less than 1Mb, but when this size is exceeded we could increase
the array size just by 1Mb when required. This slightly decreases
performance (due to more frequent array reallocation) but also
decreases the amount of unused memory in the most cases.


======================================================================
                                     
2004-08-20



Hardware and Software, Engineered to Work Together