【问题标题】:java imageio memory leakjava imageio内存泄漏
【发布时间】:2013-09-10 07:29:33
【问题描述】:

我有两个 Java 应用程序,它们都使用 ton 内存,并且都使用 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 只是没有归还它不再使用的内存空间。

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

【问题讨论】:

  • 使用大量内存不一定是内存泄漏。它会不断增长并耗尽内存吗?是不是你的内存需求在1G左右?
  • 考虑发布SSCCE;每个人都会更容易评估您遇到的问题。
  • 你的意思是虚拟机不会把内存还给操作系统吗? AFAIK VM 不会那样做;一旦它向操作系统请求了内存,它就不会归还。
  • 顺便说一句,你真的应该在第一个例子中调用 outputFile.close()。
  • 另外,inputFile 每次循环都超出范围,因此每次循环都会收集垃圾并重新分配。我在分析测试中观察到了这一点,因为即使我在循环中批量调整 1000 多张图像的大小,java.io.File 的实时实例数也永远不会超过 90。

标签: java memory-leaks javax.imageio


【解决方案1】:

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

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

【讨论】:

  • 我在 ServletContext 中使用 ImageIO 时也遇到了这个问题,并且根本无法弄清楚可能是什么问题,它绝对与并发无关,因为我只在一种方法中使用了 ImageIO那是同步的。最后安装了 TwelveMonkeys (github.com/haraldk/TwelveMonkeys) 并使用自定义代码来确保始终使用来自 TwelveMonkeys 的 ImageReader,以防格式为 JPEG。现在我的图像数据被垃圾收集得很好,一切正常。
  • 试过了,对我没有帮助,因为com.twelvemonkeys.imageio.plugins.jpeg.JPEGImageReader.read 只是调用com.sun.imageio.plugins.jpeg.JPEGImageReader.read 并将代码驱动到我认为泄漏所在的位置。这个泄漏已经被报告过很多次了,似乎没有人有解决方案
【解决方案2】:

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

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

【讨论】:

  • 请注意,您不能强制 Java GC 运行。调用System.gc() 明确建议JVM 运行垃圾收集器,但您不能确定调用后内存是否会被释放。在实践中,它会因您的 JVM 而异。
  • 我对垃圾回收的了解比这多一点。堆空间从不被收集。我可以让它坐几个小时,它永远不会被释放。曾经。我知道我无法控制它何时被收集,但 JVM 并没有那么低效,它会让它坐在那里那么久。所以,我真的不认为它像“哦,它还没有被 GCd”那么简单。
  • 哦,我也尝试过用其他进程填充剩余的内存,希望操作系统会向 JVM 发送信号说,“嘿,你真的用了那么多堆空间,因为我们真的可以使用其中的一些。”永远不会发生。我已经使用了大约 95% 的物理内存,此时整个操作系统基本上停止响应,并且有问题的 Java 应用程序没有放弃任何基础。而且,是的,这一切都是在图像保存完成并且应用程序处于空闲状态之后。
  • 我还没有看到你在哪个操作系统上的提及 - 似乎你对 JVM 和 GC 有一点线索,所以它可能归结为操作系统如何处理内存。跨度>
  • java 版本 "1.7.0_25" Java(TM) SE Runtime Environment (build 1.7.0_25-b17) Java HotSpot(TM) 64-Bit Server VM(build 23.25-b01,混合模式)
【解决方案3】:

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

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

【讨论】:

  • 在尚未绘制(blitted)到本机组件上的 BufferedImage 上调用 flush() 基本上是无操作的。它释放堆上分配的任何内存。
  • 嗯。就我个人而言,它解决了我的问题,bufferedImage 从未被删除。我只是将它变红(双循环)以制作openGL纹理。如果没有刷新,我的内存使用量(非常)过多。
  • 这是有道理的,因为 OpenGL 是原生的。在这种情况下,只需让tempImage 超出范围并让 GC 完成它的工作就足够了。
  • 我想我明白了,但我没有使用任何 BufferedImage 方法进行 blit,我只是将像素数据变红(然后,这些整数在哪里转换等)
  • 在使用后将 .flush() 添加到所有 BufferedImage 实例没有帮助。
【解决方案4】:

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

【讨论】:

  • 在尚未绘制(blitted)到本机组件上的 BufferedImage 上调用 flush() 基本上是无操作的。它释放堆上分配的任何内存。不过同意#2。 :-)
  • 在使用后将 .flush() 添加到所有 BufferedImage 实例没有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-09-29
  • 2015-08-14
  • 2012-08-11
  • 2015-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多