IMHO,你低估了你的堆空间要求。
BufferedImage
至少需要400MB(100_000_000像素,每个像素4字节)。ByteArrayOutputStream
从32字节的缓冲区开始,当没有足够的空间写入数据时,每次都会增加缓冲区的大小,每次至少要把缓冲区的大小增加一倍。
ByteArrayOutputStream
中的最终缓冲区大小是536_870_912内存消耗的关键点是这一行return stream.toByteArray();
。
BufferedImage
不能被垃圾收集(400MB)。ByteArrayOutputStream
包含一个约540MB的缓冲区你可以通过预先调整ByteArrayOutputStream
(大约240MB)来减少所需的内存。
public byte[] imageAsBytes (BufferedImage image) {
int imageSize = image.getWidth()*image.getHeight()*3 + 54; // 54 bytes for the BMP header
try (ByteArrayOutputStream stream = new ByteArrayOutputStream(imageSize)) {
ImageIO.write(image, "bmp", stream);
return stream.toByteArray();
} catch (IOException ex) {
throw new RuntimeException(ex);
另一个问题。
createNewChart()
方法被标记为@Transactional
。我不知道你的数据库,你用于数据库访问和事务管理的技术(JPA?)或者图片如何有效地存储在数据库中(BLOB?)
最有可能的是,整个数据库堆栈(在这个术语中,我也包括像JPA这样的持久化框架)将所有图片保存在内存中,直到整个事务被写入。
为了验证这个假设,你可以从createNewChart()
删除@Transactional
属性,这样图表和片段就会在不同的事务中存储到数据库中。
这样看来,你的应用程序中的某些东西在你期望它们被释放时还保留着对内存的引用。仅仅从源代码来诊断这个问题是不可能的(IMHO)--它可能是你写的东西,可能是你使用的库中的东西。
为了检测是什么保留了这些引用,你应该首先在OutOfMemoryError发生时创建一个堆转储。你可以通过在JVM的选项中加入-XX:+HeapDumpOnOutOfMemoryError
来做到这一点,像这样。
java -XX:+HeapDumpOnOutOfMemoryError -Xmx3G ..remaining start options..
这将在OutOfMemoryError发生时产生一个java_pid<somenumber>.hprof
文件。