The problem
Few days ago, a reader contacted me with a problem he had in a piece of code. The code was creating thumbnails from images and was throwing an OutOfMemoryError
after few dozens of images. Here is the simplified code, line 26 ImageIO.read(file)
, throws OutOfMemoryError
:
package com.opcodesolutions.demo;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Thumbnails {
public static void main(String[] args) throws IOException {
List<Image> thumbnails = new ArrayList<>();
for (File file : new File("/path/to/images/").listFiles()) {
if (!file.getName().endsWith(".jpg")) {
// File is NOT a JPEG
continue;
}
BufferedImage fullSizeImage = ImageIO.read(file); // OutOfMemoryError
if (fullSizeImage == null) {
// Could not parse JPEG, just ignore
continue;
}
// Create thumbnail
Image thumbnail = fullSizeImage
.getScaledInstance(150, 150, Image.SCALE_DEFAULT);
thumbnails.add(thumbnail);
}
// Do more stuff with thumbnails
}
}
And here is the stacktrace with the OutOfMemoryError
:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.awt.image.DataBufferByte.<init>(DataBufferByte.java:92)
at java.awt.image.ComponentSampleModel.createDataBuffer(ComponentSampleModel.java:415)
at java.awt.image.Raster.createWritableRaster(Raster.java:941)
at javax.imageio.ImageTypeSpecifier.createBufferedImage(ImageTypeSpecifier.java:1073)
at javax.imageio.ImageReader.getDestination(ImageReader.java:2896)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1066)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1034)
at javax.imageio.ImageIO.read(ImageIO.java:1448)
at javax.imageio.ImageIO.read(ImageIO.java:1308)
at com.opcodesolutions.demo.Thumbnails.main(Thumbnails.java:26)
The cause of the OutOfMemoryError
The cause of that problem is not obvious. The stacktrace (and the title of this article) is somewhat misleading. Here is what happens: The call to fullSizeImage.getScaledInstance()
(lines 33-34) produces a smaller image thumbnail, but that thumbnail object keeps a reference to the fullSizeImage
. Since JPEG files are highly compressed, reading and parsing them takes a significant amount of memory and that memory can never be freed.
The solution: Do not use Image.getScaledInstance()
The solution was to replace the call to fullSizeImage.getScaledInstance()
with the lines 34-38 highlighted below. That solution allowed the code to read thousands of images, because the fullSizeImage
was no longer kept in memory.
package com.opcodesolutions.demo;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
public class Thumbnails {
public static void main(String[] args) throws IOException {
List<Image> thumbnails = new ArrayList<>();
for (File file : new File("/path/to/images/").listFiles()) {
if (!file.getName().endsWith(".jpg")) {
// File is NOT a JPEG
continue;
}
BufferedImage fullSizeImage = ImageIO.read(file);
if (fullSizeImage == null) {
// Could not parse JPEG, just ignore
continue;
}
// Create thumbnail
BufferedImage thumbnail = new BufferedImage(150, 150,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g = thumbnail.createGraphics();
g.drawImage(fullSizeImage, 0, 0, 150, 150, null);
g.dispose();
thumbnails.add(thumbnail);
}
// Do more stuff with thumbnails
}
}
References
From Oracle’s website: How do I create a resized copy of an image?
For this particular problem, I did not need to produce a heap dump, because the code was small enough. With a few tests and a few searches on Google, I could figure-out what was happening. However, if you have no idea where the OutOfMemoryError
comes from, you may want to read this article: How to fix java.lang.OutOfMemoryError: Java heap space.
Author: Jonathan Demers