9

我有两个 Java 应用程序,它们都使用大量内存,并且都使用 ImageIO.write()。到目前为止,这是我发现两者之间唯一的共同点。

一个在循环中调整图像大小。另一个循环下载图像并将它们保存到磁盘。以下是相关代码:

1)

for(File imageFile : imageFilesList)
{
    if(!stillRunning) return;

    File outputFile = new File(imageFile.getAbsolutePath().replace(sourceBaseFolder.getAbsolutePath(), destinationFolder.getAbsolutePath()));
    try
    {
        outputFile.mkdirs();
        BufferedImage inputImage = ImageIO.read(imageFile);
        BufferedImage resizedImage = ImageResizer.resizeImage(inputImage, maxHeight, maxWidth);
        ImageIO.write(resizedImage, "jpg", outputFile);
    }
    catch(IOException ex)
    {
        userInterface.displayMessageToUser("IOException ocurred while converting an image: " + ex.getLocalizedMessage());
        System.out.println(outputFile.getAbsolutePath());
        ex.printStackTrace();
        return;
    }
    imagesConverted++;
    userInterface.updateTotalConvertedImages(++convertedFiles);
}

2)(在循环内)

try
{
    u = new URL(urlString);
    uc = u.openConnection();
    uc.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
    uc.connect();
    uc.getInputStream();
    in = uc.getInputStream();

    BufferedImage tempImage = ImageIO.read(in);

    String fileName = fn = ImageDownload.getFileName(u.getPath());
    fileName = outputDirString + FILE_SEPARATOR + fileName;
    while (new File(fileName).exists())
    {
        fileName = appendCopyIndicator(fileName);
    }

    ImageIO.write(tempImage, "jpg", new File(fileName));
    parent.notifyOfSuccessfulDownload(fn);
    in.close();
}
catch (FileNotFoundException ex)
{
    parent.notifyOfFailedDownload(fn);
}
catch (IOException ex)
{
    parent.handleException(ex);
}

在这两种情况下,程序都会使用大量内存。就在 RAM 的演出周围。并且当循环结束时它不会被释放。在这两种情况下,我都有一个摆动 gui 运行。当图像保存全部完成并且 gui 只是空闲时,程序有时仍在使用 1Gb+ 的内存。我什至将swing gui不直接使用的每个变量都设置null在循环之后。没有效果。

我错过了什么?

在此先感谢您的帮助。

更多信息:我刚刚在我的 IDE (Netbeans) 中分析了应用程序 1。我选择了应用程序一,因为它只处理 ImageIO(而不是网络 IO),所以这是一个更受控制的实验。

当程序在做它的事情(在循环中调整图像大小)时,总内存在大约 900,000,000 到 1,000,000,000 字节之间徘徊,而已用内存在给定时刻正在使用的总内存的大约 30% 到 50% 之间波动。

在 GC 上花费的时间从未超过 1%。

一旦实际调整大小完成并且程序进入“空闲”状态,就会发生两件事:1) 总内存停止波动并保持在 1,044,054,016 字节的静态,2) 已用内存下降到 ~14,000,000 字节 (14 mb) .

因此,看起来 JVM 只是没有归还它不再使用的内存空间。

同意?还是我误读了这个结果?

4

4 回答 4

8

我有类似的东西,当我查看分配的内存时,我看到几个大的内存块(每个大约 10-20 MB)不会被释放。使用 VirtualVM 内存转储查看它们,似乎它们被 imageIO JpegImageReader 标记为“拥有”。在这种情况下,GC 不会清除它们,因为它们被标记为由外部 GNI 调用(JpegImageReader)使用。并且 JpegImageReader 早已不复存在,所以他们只会留在那里。

就我而言,它是随机发生的。我怀疑当有许多并行调用(来自多个线程)时必须这样做,或者当 ImageReader 内部存在内部异常时,它不会释放其内存,但不是 100% 肯定。(是的,我确实刷新了缓冲的图像,处理了阅读器,并关闭了流。似乎没有帮助)。

于 2013-09-23T16:58:06.823 回答
0

您应该.flush()BufferedImage不再可用的地方使用。
例如,在您的第二个代码中:

try
{
    u = new URL(urlString);
    uc = u.openConnection();
    uc.addRequestProperty("User-Agent", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
    uc.connect();
    uc.getInputStream();
    in = uc.getInputStream();

    BufferedImage tempImage = ImageIO.read(in);

    String fileName = fn = ImageDownload.getFileName(u.getPath());
    fileName = outputDirString + FILE_SEPARATOR + fileName;
    while (new File(fileName).exists())
    {
        fileName = appendCopyIndicator(fileName);
    }

    ImageIO.write(tempImage, "jpg", new File(fileName));

    // release internal buffered memory
    tempImage.flush();

    parent.notifyOfSuccessfulDownload(fn);
    in.close();
}
于 2013-09-10T08:20:39.720 回答
0

我相信您在 #1 和 #2 的两种情况下都缺少 Image#flush() 。我建议你在 finally 子句中调用 flush() 。在 #2 中,为了更安全,也可以在 finally 子句中调用 in.close() ,因为如果您在该行之前遇到异常,那么根据您粘贴的代码片段,它很可能不会被关闭。

于 2013-09-10T08:21:03.740 回答
0

我错过了什么?了解 Java 垃圾收集的工作原理 ;-)

内存不会立即释放,只有在垃圾收集运行后才会释放。如果您愿意,可以显式调用 GC。您的操作系统也发挥了作用——例如,即使您释放了内存(用 C++ 术语),Unix 系统通常也不会真正释放内存。

于 2013-09-10T07:37:38.747 回答