Why do some Swing components cause OutOfMemoryError, but not others?

Hi all,

I have a general question here--I have a large ArrayList<ArrayList><String>> that I'm displaying in a GUI application. Each of the about 5400 ArrayList<String> is currently 10 elements long, and eventually both the number of ArrayList<String> and their lengths are going to increase. I tried using JLabels to display each element, which caused an OutOfMemoryError, and then I tried it on a JTextArea and it worked with no problem, once I got the output tab-delimited so it looked nice. I wanted to see if I could create a header row that was always visible, so I tried a JTable after that (I believe it was camickr who suggested that and also referred me to this part of the forum--thank you), but the JTable also caused an OutOfMemoryError. Does anyone know, what is it about certain Swing components that causes that error, while other components don't cause it? Is there a way to get around that? If so, is there a general way, or is it implementation-specific?

Thanks,

Jezzica85

[1044 byte] By [jezzica85a] at [2007-11-27 7:34:27]
# 1

It's just that some classes take more memory to store the same information. The available memory is global, so the first allocation that tries to go beyond that results in OOME. Most probably, a 1000 labels take more memory than 1 text area with 1000 lines (since each label has its own internal structures, including UI delegates, for example), and this is why you're seeing a OOME when you're using them.

kirillga at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 2
Hmm, that makes sense. I guess I'll stick with the JTextArea then.Thanks!Jezzica85
jezzica85a at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 3

JTable is designed to cope with large data structures, it's a fairly scalable component in that regard (since it uses delegate rendering for the cells rather than actually adding components).

Have you checked the size of your data on the back of an envelope? 5400x10 strings - how long are they? If we assume, say, 100 chars, we're looking at 5400x10x100x2 bytes = 10Mb. Scale that accordingly if your strings are longer or shorter. Average 600 characters per string and that's your default VM heap almost gone in one fell swoop.

Have you tried a profiler? (there's one in NetBeans, there's a free one you can obtain for Eclipse, there are other standalone ones) That will tell you where your memory is being spent.

Have you tried boiling your app down to a single class with the data and a table? If it still barfs, you could post that here (don't go posting reams of classes, or classes which rely on others you haven't posted).

itchyscratchya at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 4

Hmm, that makes sense. I guess I'll stick with the JTextArea then.

You almost certainly have some other, more important, issue. If switching from a JTable to a JTextArea cures your ills then either you've really loused up your usage of JTable or you're still operating just below your heap ceiling. Either way, it needs fixing, and JTextArea is going to make you suffer in the long run because it's not going to allow you to interact with each entry - JTable is almost certainly the only appropriate component to use here.

itchyscratchya at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 5
You can have column headers with your JTextArea, by the way. Check this out: http://java.sun.com/docs/books/tutorial/uiswing/components/scrollpane.html#decorations
uncle_alicea at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 6

While everybody else was commenting I was running a quick text....

I also find it interesting that JTable causes a problem. A JTable uses a single JLabel to renderer all the cells so the only memory usage is really the TableModel. Where as when you use a JTextArea you have the memory of the ArrayList plus the Document which is a copy of all the data in the ArrayList.

Here is a program that I have lying around that displays all the files on my system. It found over 80K files which means the tables holds 160K cells, which is 3 times your estimated 54K number of cells. Click on the "With Threads" button to test.

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.util.*;

import javax.swing.*;

import javax.swing.table.*;

public class TableFile extends JFrame

implements ActionListener, Runnable

{

JTable table;

DefaultTableModel model;

JTextField path;

JLabel currentFile;

JButton wrong;

JButton right;

static int totalFiles;

static int totalDirectories;

public TableFile()

{

String[] columnNames = {"IsFile", "Name"};

model = new DefaultTableModel(columnNames, 0);

table = new JTable( model );

JScrollPane scrollPane = new JScrollPane( table );

getContentPane().add(scrollPane, BorderLayout.SOUTH);

path = new JTextField("C:\\java\\j2sdk1.4.2\\demo");

getContentPane().add(path, BorderLayout.NORTH );

wrong = new JButton( "Without Threads" );

wrong.addActionListener( this );

getContentPane().add(wrong, BorderLayout.WEST );

right = new JButton( "With Threads" );

right.addActionListener( this );

getContentPane().add(right, BorderLayout.EAST );

currentFile = new JLabel(" ");

//getContentPane().add(currentFile, BorderLayout.SOUTH);

}

public void actionPerformed(ActionEvent e)

{

model.setNumRows(0);

if (e.getSource().equals(wrong))

run();

else

new Thread( this ).start();

table.requestFocus();

}

public void run()

{

totalFiles = 0;

totalDirectories = 0;

listFiles( new File( path.getText() ) );

System.out.println("Directories: " + totalDirectories);

System.out.println("Files: " + totalFiles);

}

private void listFiles(File dir)

{

updateTable( dir );

totalDirectories++;

System.out.println("Processing directory: " + dir);

// This sleep code is just added to dramatize the effect

// of executing code in the event thread

//try { Thread.sleep(10); }

//catch(Exception e) {}

File[ ] entries = dir.listFiles( );

int size = entries == null ? 0 : entries.length;

for(int j = 0; j < size; j++)

{

if (entries[j].isDirectory( ))

{

listFiles( entries[j] );

}

else

{

updateTable( entries[j] );

currentFile.setText( entries[j].toString() );

totalFiles++;

}

}

}

private void updateTable(final File file)

{

SwingUtilities.invokeLater(new Runnable()

{

public void run()

{

Vector row = new Vector(2);

row.addElement( new Boolean( file.isFile() ) );

row.addElement( file.toString() );

model.addRow( row );

}

});

}

public static void main(String[] args)

{

TableFile frame = new TableFile();

frame.setDefaultCloseOperation( EXIT_ON_CLOSE );

frame.pack();

frame.setLocationRelativeTo( null );

frame.setVisible(true);

}

}

camickra at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 7

To itchyscratchy,

Why would I still be operating just under my heap ceiling? I've saved that JTextArea as a text file using my application and it's only 247 KB. Could it be that I would just need to make lots of smaller JTables, rather than one big one? I've done a search and come across something called "paging", but I'm not really sure what that's supposed to mean or how it works.

To uncle_alice,

That link is very interesting, but I'm not sure I understand how that applies to giving column headers to a JTextArea. Is it saying that I would need to create a new class to hold the column headers?

To camickr,

That is strange that JTable can sometimes handle this stuff; maybe the problem is something to do with my implementation. So, basically what you're saying is, a JTextArea takes up more memory than a JTable?

Thanks to all of you for your suggestions,

Jezzica85

Message was edited by:

jezzica85

jezzica85a at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 8

What I'm talking about is basically a hack. The header view component would just be a JLabel or JTextField or something, and you would have to tweak the spacing of the individual "headers" just like you did with the columns in the JTextArea. If you can use a JTable instead, you should definitely go that route.

uncle_alicea at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 9

> So, basically what you're saying is, a JTextArea takes up more memory than a JTable?

When you add text from an ArrayList to the Document of the text area then the text is defined in two places because the Document has its own internal structure for storing text.

If you just create a TableModel and use a method like setValueAt(...) for a given cell, then you are just moving references around so the ArrayList and TableModel would still point to the same String in memory. So you would have the overhead of an ArrayList and a Vector, but the String would not exist twice in memory.

camickra at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 10

Why would I still be operating just under my heap ceiling?

What I mean is that the memory overheads of JTable and JTextArea are not large. If JTextArea works and JTable doesn't then, by simple logic, one must deduce that either:

a) whatever you've done with JTable has ended up using piles of extra memory (which it shouldn't); or

b) the difference in memory usage between JTable and JTextArea is inherently enough to tip you over the heap ceiling

If b) is true, then, given my previous statement (that the memory costs of JTable and JTextArea are not large), that difference is very small. The difference between your heap usage and the heap ceiling would thus be smaller than something which is small :)

This seems highly unlikely - it's far more likely that you're doing something odd with your table implementation.

I've saved that JTextArea as a text file using my application and it's only 247 KB

You have 5400x10 strings that come out at 247kb? If that's a unicode file then that means your average string length is 247000/5400/10/2 = 2 characters...?

Or have I misunderstood something?

I've done a search and come across something called "paging", but I'm not really sure what that's supposed to mean or how it works.

Depends on what context. I assume you're referring to paginating rows of results. If you're yanking rows out of a database, for instance, it's normal to load them in batches to save resources all round. So if you have 5400 records in a database (which is rather more than anyone can digest in one go anyway), you might extract the first 100 and place those in a table, providing the user a means to move to other pages - think Google results. This gets a little more involved if you're allowing the user to sort by different columns but it's still not that hard.

itchyscratchya at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 11

OK, so I guess what I'm gathering here is that making pages of tables would probably be the best way to go. I'll give that a shot.

I do have some very short strings in my display--basically, the display I'm referring to is a word frequency display, so only one column ever has strings that are longer than 7 characters; all the rest are numbers.

I wouldn't be surprised if I was doing something weird with my table. The problem is that I can't explicitly declare an array of String literals to use as column headers. The first column would always be "Word" and the last column would always be "Total", but the number of columns in the middle would be variable and run from 1 to however many "chapters" are in a file. I was trying to do all that with arrays and I probably made a mistake. Wait a minute, I just thought of something--isn't it possible to convert an ArrayList<String> into a String[] somehow? That would fix things with column headings, probably...

Thanks again,

Jezzica85

jezzica85a at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...
# 12
> isn't it possible to convert an ArrayList<String> into a String[] somehow?Just use a Vector. The DefaultTableModel will use the Vector for its column names and no conversion is required.
camickra at 2007-7-12 19:14:56 > top of Java-index,Desktop,Core GUI APIs...