2

我必须创建一个函数来接收条目 aList<PDXObjectImage> list并为每个元素创建一个小图标并将它们存储在 JTable 中。

现在我找到了一种从 a 创建图标PDXObjectImage而不加载整个图像的方法,这样我的程序就不会抛出OutOfMemoryError: Java heap space

for(int k=0;k<list.size();k++)
{
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    list.get(k).write2OutputStream(output);
    ByteArrayInputStream bais = new ByteArrayInputStream(output.toByteArray());
    ImageInputStream iis = ImageIO.createImageInputStream(bais);
    Iterator iter = ImageIO.getImageReaders(iis);
    if (iter.hasNext()) {
        ImageReader reader = (ImageReader) iter.next();
        reader.setInput(iis, true, true);
        ImageReadParam params = reader.getDefaultReadParam();
        params.setSourceSubsampling(2.0, 2.0, 0, 0);
        BufferedImage img = reader.read(0, params);
        ImageIcon imageIcon = new ImageIcon(img);
        model.addRow(new Object[]{imageIcon});
    }
}

我设法避免OutOfMemoryError: Java heap space使用阅读器来避免列表中的少量图片,而不是BufferedImage每次都加载图像。不幸的是,当列表中存储超过 84 个元素时,我仍然会收到此错误。

我使用 jvisualvm 查看哪些对象占用了所有堆空间,我发现它是byte[]对象(大约 85%)。

问题显然位于我创建所有流以获取iconImage. 问题是我不知道ImageInputStream无需每次都创建新流的任何方法。

我试图通过在一个函数中生成所有流来避免这个问题:

private ImageInputStream fct(PDXObjectImage img) throws IOException{
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    img.write2OutputStream(output);
    ByteArrayInputStream bais = new ByteArrayInputStream(output.toByteArray());
    return ImageIO.createImageInputStream(bais);
}

认为java会在到达范围末尾时自动删除对象。

我尝试在每个循环结束时以任何可能的顺序添加以下内容:

output.reset();
output.flush();
bais.reset();
bais.close();
iis.flush();
output=null;
bais=null;
iis=null;
System.gc();

我还尝试在函数范围之外实例化流,但是如果不使用关键字就无法ByteArrayInputStream从 a设置 a ,从而创建一个新对象。byte[]new

我仍然遇到同样的错误,没有任何效果。

我阅读了一些帖子StatementsResultSets但我发现它们并不相关。(也许我错了)

如果有人知道如何避免此错误,我将不胜感激。

谢谢

编辑:

我修改了我的代码,以便获得以下信息:

for(int k=0;k<list.size();k++)
{
    list.get(k).write2OutputStream(cbb.getOutputStream());
    ImageInputStream iis = ImageIO.createImageInputStream(cbb.getInputStream());
    Iterator iter = ImageIO.getImageReaders(iis);
    if (iter.hasNext()) {
        ImageReader reader = (ImageReader) iter.next();
        reader.setInput(iis, true, true);
        BufferedImage img = reader.read(0, null);
        ImageIcon imageIcon = new ImageIcon(img);
        model.addRow(new Object[]{imageIcon});
    }
}

我还为阅读器添加了一个监听器,以便它打印出阅读完成的百分比。它总是上升到 84.2% 并停止。

有谁知道这怎么可能?

4

3 回答 3

2

使用PipedInputStream/PipedOutputStreamCircularByteBuffer引导直接写入输入流的字节。这样您就不必创建中间流和浪费内存。

看看这个帖子:

http://ostermiller.org/convert_java_outputstream_inputstream.html

重写fct方法使用CircularByteBuffer

private ImageInputStream fct(PDXObjectImage img) throws IOException{
CircularByteBuffer cbb = new CircularByteBuffer(CircularByteBuffer.INFINITE_SIZE);
img.write2OutputStream(cbb.getOutputStream());
return ImageIO.createImageInputStream(cbb.getInputStream());
}

您还可以使用多线程方法,在一个线程中写入字节并在另一个线程中读取。因此,写入/读取可以同时进行,以优化 CPU 使用率和内存使用率。

注意:com.Ostermiller.util.CircularByteBuffer不是标准的 Java API。但是源码是免费的

于 2013-06-06T13:02:48.033 回答
1

1)您的代码中没有任何内容表明加载的图像无论如何都被压缩为“小图标”,除了Iterator iter = ImageIO.getImageReaders(iis);在线。你能确认图像确实被压缩了吗?否则,可能是没有足够的 RAM 分配给 JVM 堆的简单情况;

2) 即使图像被压缩到更小的字节体积,你可能仍然没有足够的 RAM 分配给 JVM。或者您的压缩图像可能对未压缩图像有内部引用(我不熟悉使用的 API,所以我不能确定),导致更大的图像保留在 JVM 堆中。使用内存分析器,查看循环的每次迭代占用了多少内存,以及它是否由于 GC 而随着时间的推移而减少。然后将总堆除以这个数字,您可能知道可以加载多少个图标而不获取OutOfMemory.

3)StatementsResultSetsJava图像处理无关,这些与使用关系数据库有关。

于 2013-06-06T12:53:11.293 回答
0

您只是将两个维度的图像大小减半(使用子采样参数)。创建图标应缩放图像,以使图像更大的尺寸与图标的显示尺寸相匹配。

您需要首先确定图像尺寸,然后计算适当的目标尺寸,然后通过二次采样读取图像。

如果您的图标大小是(例如)100 x 100 像素,则图标的较大尺寸应该正好是 100 像素。

于 2013-06-06T14:23:13.863 回答