添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

我需要创建一个大图像(w<=20k,h<=100k)。 我把它分成几个片段(w=20k,h=5k)并在一个循环中把它们存储在数据库中。 但我仍然得到一个内存不足的错误。在堆的1.5千兆字节。 为什么会出现内存泄漏?根据我的计划,只有当前的片段,重约300兆字节,应该存储在堆中。

@Transactional
public long createNewChart (int width, int height) {
    Chartographer chartographer = new Chartographer(width, height);
    chartRepo.save(chartographer);
    int number = 0;
    while (height >= 5000) {
        BufferedImage fragment = new BufferedImage(width, 5000, BufferedImage.TYPE_INT_RGB);
        fragmentRepo.save(new Fragment(imageOperator.imageAsBytes(fragment), number, chartographer));
        height -= 5000;
        number++;
    if (height > 0) {
        BufferedImage fragment = new BufferedImage(width, height % 5000, BufferedImage.TYPE_INT_RGB);
        fragmentRepo.save(new Fragment(imageOperator.imageAsBytes(fragment), number, chartographer));
    return chartographer.getCharId();
public byte[] imageAsBytes (BufferedImage image) {
    try(ByteArrayOutputStream stream = new ByteArrayOutputStream()) {
        ImageIO.write(image, "bmp", stream);
        return stream.toByteArray();
    } catch (IOException ex) {
        throw new RuntimeException(ex);

在此输入图片描述

2 个评论
你是否使用了一个缓存的ORM框架,如JPA?如果是,请检查Fragment对象是否被缓存了。
BufferedImage.TYPE_INT_RGB 改为TYPE_3BYTE_BGR 应该可以减少大约1/4的BufferedImages。另外,如果你可以直接从数据库(甚至磁盘)写到一个流,而不是ByteArrayOuputStream/byte[] 的解决方案,这可能会节省你大量的内存。
java
database
image
Tidus213
Tidus213
发布于 2022-02-11
2 个回答
Thomas Kläger
Thomas Kläger
发布于 2022-02-11
0 人赞同

IMHO,你低估了你的堆空间要求。

  • 对于20_000乘5_000像素,BufferedImage 至少需要400MB(100_000_000像素,每个像素4字节)。
  • ByteArrayOutputStream 从32字节的缓冲区开始,当没有足够的空间写入数据时,每次都会增加缓冲区的大小,每次至少要把缓冲区的大小增加一倍。
  • 在我的测试中,ByteArrayOutputStream 中的最终缓冲区大小是536_870_912
  • 因为这个大小可能是通过将以前的大小翻倍来达到的,所以它暂时需要1.5倍的内存
  • 另一个300_000_054字节的最终字节数组来存储最终的字节数。
  • 内存消耗的关键点是这一行return stream.toByteArray();

  • BufferedImage 不能被垃圾收集(400MB)。
  • ByteArrayOutputStream 包含一个约540MB的缓冲区
  • 最后一个字节数组的内存需要分配(另外300MB)。
  • 在这个特定点上,仅图像处理就需要 1240MB内存(不考虑你的应用程序的其他部分所消耗的所有内存)。
  • 你可以通过预先调整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 文件。