当拼接13k个.png图像时,出现了java.lang.OutOfMemoryError: Java heap space错误。

3
我有13255张图片,每张240 x 240像素宽,最大为15412字节,最小为839字节。
我试图循环遍历文件夹,将它们添加到一个File[]中。一旦我有了每个图像的数组,我就将它们放入一个BufferedImage[]中,准备循环遍历并绘制成由每个单独图像组成的更大的单一图像。
每个图像的命名形式如下:
Image x-y.png
然而,我不断遇到java.lang.OutOfMemoryError:Java堆空间错误。我不知道为什么。我尝试通过在Eclipse目标的末尾添加参数来修改JVM可用内存大小。以下是我使用的内容:
IDE's\eclipse-jee-juno-SR2-win32-x86_64\eclipse\eclipse.exe -vmargs -Xms64m -Xmx1024m

and

IDE's\eclipse-jee-juno-SR2-win32-x86_64\eclipse\eclipse.exe -vmargs -Xms64m -Xmx4096m

两者都没有效果。我还进入了控制面板 -> 程序 -> Java 并更改了可用内存的数量。

这是我编写的方法:

public static void merge_images() throws IOException {
        int rows = 115; 
        int cols = 115;
        int chunks = rows * cols;

        System.out.println(chunks);

        int chunkWidth, chunkHeight;
        int type;
        // fetching image files
        File[] imgFiles = new File[chunks];

        int count = 0;
        for (int j = 1; j <= 115; j++) {
            for (int k = 1; k <= 115; k++) {
                imgFiles[count] = new File("G:\\Images\\Image " + j
                        + "-" + k + ".png");
                count++;
            }
        }
        System.out.println(imgFiles.length);

        // creating a buffered image array from image files
        BufferedImage[] buffImages = new BufferedImage[chunks];
        for (int i = 0; i < chunks; i++) {
            buffImages[i] = ImageIO.read(imgFiles[i]);
            System.out.println(i);
        }
        type = buffImages[0].getType();
        chunkWidth = buffImages[0].getWidth();
        chunkHeight = buffImages[0].getHeight();

        // Initializing the final image
        BufferedImage finalImg = new BufferedImage(chunkWidth * cols,
                chunkHeight * rows, type);

        int num = 0;
        for (int i = 0; i < rows; i++) {
            for (int k = 0; k < cols; k++) {
                finalImg.createGraphics().drawImage(buffImages[num], null,
                        chunkWidth * k, chunkHeight * i);
                num++;
            }
        }
        System.out.println("Image concatenated.....");
        ImageIO.write(finalImg, "png", new File("fusions.png"));
        System.out.println("Image Saved, Exiting");
    }

在这里打印行

for (int i = 0; i < chunks; i++) {
            buffImages[i] = ImageIO.read(imgFiles[i]);
            System.out.println(i);
}

它总是在大约7320点停止。

以下是确切的控制台输出

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at java.awt.image.DataBufferByte.<init>(Unknown Source)
    at java.awt.image.ComponentSampleModel.createDataBuffer(Unknown Source)
    at java.awt.image.Raster.createWritableRaster(Unknown Source)
    at javax.imageio.ImageTypeSpecifier.createBufferedImage(Unknown Source)
    at javax.imageio.ImageReader.getDestination(Unknown Source)
    at com.sun.imageio.plugins.png.PNGImageReader.readImage(Unknown Source)
    at com.sun.imageio.plugins.png.PNGImageReader.read(Unknown Source)
    at javax.imageio.ImageIO.read(Unknown Source)
    at javax.imageio.ImageIO.read(Unknown Source)
    at main.merge_images(main.java:48)
    at main.main(main.java:19)

任何关于我做错了什么的想法都将不胜感激。
敬礼,
Jamie

你是否在某个地方设置了断点,并且在程序抛出异常之前实际检查了程序使用的内存量?我猜测将图像加载到内存中所需的内存比存储它所需的内存要多得多,因此你实际上可能会耗尽内存,就像异常所提示的那样。 - Bernhard Barker
我碰巧进行了步骤,结果它很早就崩溃了。正如我的解决方案所显示的那样,这很可能是由于程序中有太多重复的图像数组造成的。 - Jamie
5个回答

4

您不需要将所有的块图像都存储在内存中。您可以逐一读取它们,并在最后循环中绘制到最终图像中,如下所示:

for (int i = 0; i < rows; i++) {
    for (int k = 0; k < cols; k++) {
        BufferedImage buffImage = ImageIO.read(imgFiles[num]);
        finalImg.createGraphics().drawImage(buffImage, null,
                chunkWidth * k, chunkHeight * i);
        num++;
    }
}

这将至少节省一半的内存。


1

看起来每个人都在提供一种更简单、更节省资源的解决方案。让我试试我的:

public static void merge_images() throws IOException {
    int rows = 115; 
    int cols = 115;

    System.out.println(rows * cols);

    // Initializing the final image
    BufferedImage finalImg = null;
    for (int i = 0; i < rows; i++) {
        for (int k = 0; k < cols; k++) {
            BufferedImage bufferedImage = ImageIO.read(new File("G:\\Images\\Image " + i + "-" + k + ".png"));
            int chunkWidth = bufferedImage.getWidth();
            int chunkHeight = bufferedImage.getHeight();
            if (finalImg == null) {
                finalImg = new BufferedImage(chunkWidth * cols, chunkHeight * rows, bufferedImage.getType());
            }
            finalImg.createGraphics().drawImage(bufferedImage, null, chunkWidth * k, chunkHeight * i);
        }
    }

    System.out.println("Image concatenated.....");
    ImageIO.write(finalImg, "png", new File("fusions.png"));
    System.out.println("Image Saved, Exiting");
}

1
你需要增加程序的最大内存,而不是最大化eclipse所需的内存(我认为它不需要更多内存)。
在eclipse中有一个选项可以更改VM参数(我不知道它是什么,因为我很多年没有使用过)。
无论如何,您需要能够加载所有未压缩的图像。这意味着您至少需要13225 * 240 * 240 * 4字节。这超过了3 GB。如果您首先加载所有图像,则需要至少两倍。我建议将堆大小设置为至少4-8 GB。

0

正如hoaz指出的那样(非常感谢),我采取了比必要步骤更多的措施。我的解决方案如下:

public static void merge_images() throws IOException {
        int rows = 115; // we assume the no. of rows and cols are known and each
                        // chunk has equal width and height
        int cols = 115;
        int chunks = rows * cols;

        System.out.println(chunks);

        // fetching image files
        File[] imgFiles = new File[chunks];

        int count = 0;
        for (int j = 1; j <= 115; j++) {
            for (int k = 1; k <= 115; k++) {
                imgFiles[count] = new File("G:\\Images\\Image " + j
                        + "-" + k + ".png");
                count++;
            }
        }

        // Initializing the final image
        BufferedImage finalImg = new BufferedImage(240 * cols,
                240 * rows, 13);

        int num = 0;
        for (int i = 0; i < rows; i++) {
            for (int k = 0; k < cols; k++) {
                BufferedImage buffImage = ImageIO.read(imgFiles[num]);
                finalImg.createGraphics().drawImage(buffImage, null,
                        240 * k, 240 * i);
                num++;
            }
        }
        System.out.println("Image concatenated.....");
        ImageIO.write(finalImg, "png", new File("fusions.png"));
        System.out.println("Image Saved, Exiting");
    }

本质上,我所做的是删除了 BufferedImage[],而是在嵌套的 for 循环中创建了一个新的 BufferedImage,当我正在构建最终图像时。我没有使用变量,而是硬编码了图像的边界和每个图像的类型。

感谢 hoaz 指引我正确的方向。

敬礼

Jamie


0
尝试使用 -Xmx1024m 或更大的值来运行 JVM。这个值是堆大小。
java -Xmx1024m -jar app.jar

此外,您可以在IDE中增加此值。


好的开发人员总是通过清理和整洁的代码寻求解决方案,这可能是配置JVM的答案。当您确信没有进一步改进代码的空间时,那么这可以是一个解决方案。 - kAmol

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接