java.lang.OutOfMemoryError:将 13k .png 图像拼接在一起时的 Java 堆空间

Posted

技术标签:

【中文标题】java.lang.OutOfMemoryError:将 13k .png 图像拼接在一起时的 Java 堆空间【英文标题】:java.lang.OutOfMemoryError: Java heap space when stitching 13k .png images together 【发布时间】:2013-05-10 14:23:52 【问题描述】:

我有 13255 张图片,每张 240 x 240 像素宽,最大的 15,412 字节,最小的 839 字节。

我正在尝试遍历将它们中的每一个添加到文件 [] 的文件夹。一旦我有了每个图像的数组,我就会将它们放在一个 BufferedImage[] 中,准备好循环并绘制到由每个单独的图像组成的更大的单个图像上。

每张图片都以

的形式命名

图像 x-y.png

但是,我总是遇到 java.lang.OutOfMemoryError: Java heap space 错误。我不知道为什么。我尝试通过在 Eclipse 目标的末尾添加参数来更改 JVM 可用内存的大小。以下是我用过的:

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

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)

我将不胜感激任何想法。

问候,

杰米

【问题讨论】:

您是否在某处放置了一个断点并实际检查了程序在抛出异常之前使用了多少内存?我猜想将图像加载到内存中所消耗的内存比存储它所花费的内存要多得多,因此正如例外情况所暗示的那样,您实际上内存不足的可能性不大。 我确实是巧合地通过了,而且它更早崩溃了。正如我的解决方案所示,这很可能是由于程序中有太多图像在数组中重复。 【参考方案1】:

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

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++;
    

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

【讨论】:

【参考方案2】:

您需要增加程序的最大内存,而不是我认为不需要更多内存的 eclipse 的最大值。

eclipse中有一个用于更改VM参数的选项(我不知道是因为我多年没有使用它)

同样,您需要能够加载所有未压缩的图像。这意味着您至少需要 13225 * 240 * 240 * 4 个字节。这刚刚超过 3 GB。如果首先加载所有图像,则至少需要两倍。我建议您将堆至少设置为 4 - 8 GB。

【讨论】:

【参考方案3】:

看起来每个人都在提供更简单且资源消耗更少的解决方案。让我试试我的:

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");

【讨论】:

【参考方案4】:

尝试使用 -Xmx1024m 或更大的值运行 JVM。这个值就是堆大小。

java -Xmx1024m -jar app.jar

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

【讨论】:

这可以作为配置 JVM 的答案,但是优秀的开发人员总是通过代码的清洁和整洁来寻求解决方案。当您确定没有进一步改进代码的余地时,这可能是一个解决方案。【参考方案5】:

正如 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。我没有使用变量,而是硬编码了图像的边界和每个图像的类型。

感谢您为我指明正确的方向。

问候

杰米

【讨论】:

以上是关于java.lang.OutOfMemoryError:将 13k .png 图像拼接在一起时的 Java 堆空间的主要内容,如果未能解决你的问题,请参考以下文章