Java Swing: Read many images files without memory problems?
I'm writing a Java Swing application that will load from disk about 1500 png images that range in size from 50kb to 75kb each. I don't need to load all of them at once, but I will need to load at 50 images at a time. I've been trying to load them using the typical:
new javax.swing.ImageIcon(getClass().getResource("myimage.jpeg")
but my application freezes and I run out of memory after about the first 30 images or so.
What is the best way to load these images in such a way that I will not overload the jvm and that i will be able to monitor which have loaded successfully so far? If possible and necessary, I'd wouldn't mind if the application showed a "loading..." screen while the images loaded, but I'm开发者_StackOverflow社区 not sure how to do that.
Is caching useful here? I don't quite understand it, but I saw this question about using MediaTracker and I'm not sure how that could be implemented here.
Why not create a wrapper object for each image, load them on-demand, and make use of WeakReferences or SoftReferences. That way the garbage collector can bin the image data when necessary, and you can reload as/when the weak reference is cleared.
The upside is that the memory for the images can be cleared when required for other uses. The downside is that you will have to reload the image prior to display (or whatever you're doing with the image).
If you already know how many .pngs you are going to load, you may want to create an ImageIcon Array and load them one by one from the directory/directories (which would allow you to display a loading... screen).
What I think you should do is increasing the min/max. HeapSize of the JVM when running the application. You can specify them by e.g. adding -Xmx256m as a parameter (this sets the max-heap to 256MB) (and maybe -Xms32m [this sets the min-heap to 32mb]) see http://docs.sun.com/source/817-2180-10/pt_chap5.html#wp57033
You will either add these options when launching your app (e.g. "java -jar myApp.jar -Xmx128m") or to your system's jvm-configuration-file or to your project's build properties.
This piece of code would load the entire directory; if you want only 50 images to be loaded, just fiddle with the start and stop parameters.
As already said, you will have to set the max-heap (-Xmx) to something around 300M (e.g. resolution of 1280x1024 -> 1310720px -> 4 byte/pixel -> 5242880 bytes -> 50 images -> 256MB).
File dir = new File("/path/to/directory");
File[] files = dir.listFiles();
BufferedImage[] images = new BufferedImage[files.length];
for (int i = 0; i < files.length; i++)
{
try
{
images[i] = ImageIO.read(files[i]);
} catch (IOException ex){}
}
As stated by Tedil, you should give more memory to the app by launching with something like:
java -Xmx256m -classpath yourclasspath YourMainClass
To load the images with a "please wait" loading screen and a progress bar is tricky. It's already in the realm of Advanced Swing. If you are using Java 6 I recommend reading up the SwingWorker class.
Here's a demo that shows you one approach:
package com.barbarysoftware;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.image.BufferedImage;
import java.util.List;
public class ImageLoadingDemo {
public static void main(String[] args) {
final JFrame frame = new JFrame();
frame.setPreferredSize(new Dimension(600, 400));
frame.getContentPane().add(new JLabel("I'm the main app frame", JLabel.CENTER));
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
final JDialog pleaseWaitDialog = new JDialog(frame, "Loading images", true);
final int imageCount = 50;
final JProgressBar progressBar = new JProgressBar(0, imageCount);
final BufferedImage[] images = loadImages(frame, pleaseWaitDialog, imageCount, progressBar);
System.out.println("images = " + images);
}
private static BufferedImage[] loadImages(JFrame frame, final JDialog pleaseWaitDialog, final int imageCount, final JProgressBar progressBar) {
final BufferedImage[] images = new BufferedImage[imageCount];
SwingWorker<Void, Integer> swingWorker = new SwingWorker<Void, Integer>() {
@Override
protected Void doInBackground() throws Exception {
for (int i = 0; i < imageCount; i++) {
System.out.println("i = " + i);
publish(i);
Thread.sleep(1000); // to simulate the time needed to load an image
// images[i] = ImageIO.read(new File("... path to an image file ..."));
}
return null;
}
@Override
protected void process(List<Integer> chunks) {
final Integer integer = chunks.get(chunks.size() - 1);
progressBar.setValue(integer);
}
@Override
protected void done() {
pleaseWaitDialog.setVisible(false);
}
};
JPanel panel = new JPanel();
panel.add(progressBar);
panel.add(new JButton(new AbstractAction("Cancel") {
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}));
pleaseWaitDialog.getContentPane().add(panel);
pleaseWaitDialog.pack();
pleaseWaitDialog.setLocationRelativeTo(frame);
swingWorker.execute();
pleaseWaitDialog.setVisible(true);
return images;
}
}
You should be very careful with loading files through the classloader since these resources are not freed while the classloader is active.
Instead use another approach to load the files directly from disk using a java.io.File object. THey can then be discarded without laying invisibly around.
What are you going to do with the images? Do you really need ImageIcon instances, or will an Image do as well? In either case, it may be easier for you to control the loading if you use the synchronous methods in ImageIO instead of the ImageIcon constructors.
If you run into OutOfMemoryErrors already after 30 images, I assume that the images may have a high resolution and/or colour depth, even if they are relatively small on disk. When you load an image, the image is decompressed and requires much more memory (usually 4*width*height bytes for a colour image) than the size of the compressed file. Unless the images are very small, you are probably not able to cache 1500 uncompressed images within a reasonable amount of memory, so you will have to implement some strategy to only load the images you currently need.
精彩评论