views:

581

answers:

5

Hi,

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 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.

+3  A: 

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%5Fchap5.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){}
}
Tedil
Ok. good idea, but should I still load them in a loop as ImageIcons? There has to be a better way
Yoely
You could load them in a loop as images using ImageIO, then you know it has loaded when the call returns, rather than having a separate callback to track the loading. How much memory a png image takes when loaded will depend on how many pixels it has, not the size on disk.
Pete Kirkham
What is your heap set at? If you haven't added the flags mentioned then you may just need to add more memory at startup.
broschb
Ah, so it's some sort of slideshow you're heading for. I would suggest then to preload/cache e.g. 5 images, display 1 image (for 4 seconds) and load the next (the sixth) image.
Tedil
@Tedil, The question says I need to preload/cache at least 50 images at a time, but I don't know how to do that. I'm trying to learn how to do actually do this, If you add to your answer how to preload the images, I'll mark your answer as correct
Yoely
Yoely, I'm not going to edit my answer just because you offer to mark it as correct - I'm not "hunting"/"collecting" correct answers for the sake of correct answers and points - but for helping people with their problems.
Tedil
@Tedil, I really appreciate your answer and I'm sorry that my comment appeared as a "bribe" for points. I was merely explaining that your answer was insufficient to be marked "correct" because it did not fully answer the question. Further, it's not like these points have any value outside of SO so I assume we're all here out of our love for programming and our love to help others learn how to program as well :)
Yoely
I hope you are using a SwingWorker (or other threaded system) to load the images, to prevent the application freezing. Loading images in the main thread is not a good idea.And there is a Java API which you can use to check the amount of remaining memory (see Runtime class).
extraneon
A: 

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.

Thorbjørn Ravn Andersen
Class#getResource only resolves a URL, from which the image (or whatever other resource) can be loaded. The class loader is not involved in actually loading the resource and is not caching anything.
jarnbjo
+2  A: 

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).

Brian Agnew
A: 

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.

jarnbjo
I'm making a slideshow out of the images for a psychology experiment and on another thread, I'm testing the reaction times to different types of shapes. Each image is shown for up to 4 seconds, but if the user presses spacebar, then slideshow is paused.
Yoely
What is the difference practically btwn using ImageIcon and Image? Which methods in ImageIO are synchronous?
Yoely
ImageIO.read is synchronous and will not complete until the image is read and uncompressed completely. Image is an abstract representation of an image and ImageIcon is a Swing GUI component to display images. If you create an ImageIcon with a byte array or an image source, the image may not be actually loaded and prepared until it is required for displaying the component, which may lead to odd behaviour if you have many images. If you have created an Image instance with ImageIO, you can still create an ImageIcon instance iwth that image and prevent background loading and decompression.
jarnbjo
+1  A: 

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;
    }
}
Steve McLeod