最近遇到了BufferedImage OutOfMemoryError的问题,在此记录一下。
事情的起因是项目中有个功能需要将多张图片合并为一张,流程如下:
- 有三个图片文件,
File A,File B,File C
- 通过
ImageIO.read(inputStream)
将ABC转换成BufferedImage
类型 - 通过
java.awt.image.BufferedImage#getWidth()
获取这三张图片中的最大宽度maxWidth
, - 计算另外两张图片等比例拉伸至
maxWidth
后的高度,将这三张图片的高度相加得到maxHeight
- 创建一张
maxWidth * maxHeight
的图片 - 通过画布将三张图片写入到这张图片里
主要代码如下:
1 | ... |
运行过程中发现第4
和17
行代码时不时会抛出OutOfMemoryError错误,研究了下发现有两个问题:
- 对于jpeg格式的图片,java将其载入到内存时是不会对其进行压缩的,一个像素会占用3个字节的内存,如果图片的尺寸比较大,会占用非常大的内存,比如这张图片,实际文件大小为84kb,载入到内存里后的大小为11mb:
1 | val file = File("/Downloads/fff.jpeg") |
- 合并图片前代码将这三张图片一次性全部加载到内存里,这也会占用比较大的内存。
对于问题1,目前只能规避这问题,加载图片前会预先判断下该图片会占用多少内存,对于会超出内存使用的jpeg图片不予合并。同时在代码第14行,我们对合并后的最大宽度进行限制,避免合并后的图片尺寸过大,占用的内存超出限制。
对于问题2,这三张图片可以按序加载,不用一次性全部加载到内存里,在需要合并时才加载到内存里,同时可以改进获取图片尺寸的代码,不用将图片加载到内存后再获取尺寸。
最终代码如下:
1 | fun getImageSize(file: File): Dimension { |
按上述代码修改后,再也没有发生OutOfMemoryError。对于问题1,目前发现apache commons-imaging似乎可以解决这问题,有时间去尝试下,到时候再来更新本文。
参考:
https://coderanch.com/t/416485/java/Java-BufferedImage-OutOfMemoryError