在循环中示例化对象而不获取outofmemoryerror:java堆空间

vkc1a9a2  于 2021-06-30  发布在  Java
关注(0)|答案(3)|浏览(413)

我必须做一个函数来接收 List<PDXObjectImage> list 并为每个元素创建一个小图标,并将它们存储在jtable中。
现在我找到了一种从 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();

我还尝试示例化函数作用域之外的流,但是没有办法设置 ByteArrayInputStreambyte[] 不使用 new 关键字,从而创建一个新对象。
我还是会犯同样的错误,什么都不管用。
我在网上看了一些帖子 Statements 以及 ResultSets 但我觉得它们不相关(也许我错了)
如果有人知道我怎样才能避免这个错误,我会非常感激的。
谢谢您
编辑:
我修改了我的代码,以便得到以下信息:

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

我还为reader添加了一个监听器,以便它打印出完成阅读的百分比。它总是上升到84.2%,然后停止。
有人知道这怎么可能吗?

bsxbgnwa

bsxbgnwa1#

使用 PipedInputStream/PipedOutputStream 或者 CircularByteBuffer 将直接写入的字节传送到输入流。这样您就不必创建中间流和浪费内存。
看看这个帖子:
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。但是来源是免费的

p8ekf7hl

p8ekf7hl2#

1) 代码中没有任何东西表明加载的图像被压缩为“小图标”,除了在 Iterator iter = ImageIO.getImageReaders(iis); 线路。你能确认图像确实被压缩了吗?否则,可能是没有足够的ram分配给jvm堆的简单情况;
2) 即使将映像压缩到更小的字节量,分配给jvm的ram可能仍然不足。或者您的压缩映像可能有对未压缩映像的内部引用(我不熟悉所使用的api,所以我不能确定),从而导致较大的映像保留在jvm堆中。使用MemoryProfiler,查看循环的每次迭代占用了多少内存,以及它是否随着时间的推移由于gc而减少。然后将总堆除以这个数字,您可能知道可以加载多少个图标 OutOfMemory .
3) Statements 以及 ResultSets 与java图像处理无关,这些都是关于使用关系数据库的。

envsm3lx

envsm3lx3#

您只需在两个维度中将图像大小减半(使用子采样参数)。创建图标应该缩放图像,以便更大尺寸的图像正好与图标的显示大小匹配。
首先需要确定图像的尺寸,然后计算出合适的目标尺寸,然后用子采样读取图像。
如果您的图标大小是(例如)100 x 100像素,那么图标在其较大的尺寸中应该正好是100像素。

相关问题