Почему BufferedImage требует так много памяти за пределами размера его массива данных?

голоса
4

Я пытаюсь определить , сколько кучи любого данный TYPE_INT_ARGB BufferedImageбудет использовать так , что для программы , которая делает некоторую обработку изображения, я могу установить разумную максимальную кучу на основе размера изображений мы кормить его.

Я написал следующую программу в качестве теста, который я затем используется для определения наименьшей максимальной кучи , под которым он будет работать без OutOfMemoryError:

import java.awt.image.BufferedImage;

public class Test {
  public static void main(String[] args) {
    final int w = Integer.parseInt(args[0]);
    final int h = Integer.parseInt(args[1]);

    final BufferedImage img =
      new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

    System.out.println((4*w*h) >> 20);
  }
}

(Напечатанное значение ожидаемого размера , int[]в котором BufferedImageхранятся «данные с пикселями.) То , что я ожидал, был то , что требуется не более кучи что - то вроде x + c, где xесть размер массива данных и cявляется константой , состоящей из размеры классов , которые загружены, BufferedImageобъект и т.д. Это то , что я нашел вместо этого (все значения в МБ):

4 * W * H мин макс кучи
----- ------------
  5 -
 10 15
 20 31
 40 61
 80 121
160 241

1.5xхорошо подходит для наблюдений. (Обратите внимание, что я не нашел минимум для 5MB изображения.) Я не понимаю, что я вижу. Каковы эти дополнительные байты?

Задан 04/10/2010 в 11:06
источник пользователем
На других языках...                            


4 ответов

голоса
4

Там, кажется, ошибка в виртуальной машине Oracle, введена где-то между 1.6.0_16 и 1.6.0_20. Вы даже можете уменьшить проблему выделения в целочисленном массиве, так как эта проблема связана не только с BufferedImage.

С 1.6.0_16, мне нужно как минимум 413 МБ кучу, чтобы выделить целочисленный массив с 100000000 элементами, которые кажутся разумными. С 1.6.0_20, та же операция требует, по меньшей мере, 573 Мб пространства кучи, хотя только провер 400000000 байт фактически используются после выделения массива.

Ответил 04/10/2010 в 12:37
источник пользователем

голоса
1

При дальнейшем исследовании, проблема, как представляется, что старое поколение в куче не может расширить достаточно, чтобы разместить массив данных изображения,, несмотря на, что есть достаточно свободной памяти в куче в целом.

Для получения более подробной информации о том , как расширить старое поколение, увидеть этот вопрос .

Ответил 24/11/2010 в 18:38
источник пользователем

голоса
0

Проблема состоит в том, что объект BufferedImage сохраняет изображение в памяти в uncompresed формате. Существует эффективное решение для этого: вы можете сохранить изображение на жестком диске , и вам не придется беспокоиться о размере кучи или физического предела памяти. Он может хранить максимум 2147483647 пикселей (или 46,340 х 46340 пикселей). BigBufferedImage решает эту проблему.

Создание аз пустого в BigBufferedImage:

BigBufferedImage image = BigBufferedImage.create(
        tempDir, width, height, type);

Загрузите существующий файл в BigBufferedImage:

BigBufferedImage image = BigBufferedImage.create(
        imagePath, tempDir, type);

Рендер части изображения:

part = image.getSubimage(x, y, width, height);

Реализация BigBufferedImage:

package com.pulispace.mc.ui.panorama.util;

/*
 * This class is part of MCFS (Mission Control - Flight Software) a development 
 * of Team Puli Space, official Google Lunar XPRIZE contestant. 
 * This class is released under Creative Commons CC0.
 * @author Zsolt Pocze, Dimitry Polivaev
 * Please like us on facebook, and/or join our Small Step Club.
 * http://www.pulispace.com
 * https://www.facebook.com/pulispace
 * http://nyomdmegteis.hu/en/
 */
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.color.ColorSpace;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import sun.nio.ch.DirectBuffer;

public class BigBufferedImage extends BufferedImage {

    private static final String TMP_DIR = System.getProperty("java.io.tmpdir");
    public static final int MAX_PIXELS_IN_MEMORY =  1024 * 1024;

    public static BufferedImage create(int width, int height, int imageType) {
        if (width * height > MAX_PIXELS_IN_MEMORY) {
            try {
                final File tempDir = new File(TMP_DIR);
                return createBigBufferedImage(tempDir, width, height, imageType);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        } else {
            return new BufferedImage(width, height, imageType);
        }
    }

    public static BufferedImage create(File inputFile, int imageType) throws IOException {
        try (ImageInputStream stream = ImageIO.createImageInputStream(inputFile);) {
            Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
            if (readers.hasNext()) {
                try {
                    ImageReader reader = readers.next();
                    reader.setInput(stream, true, true);
                    int width = reader.getWidth(reader.getMinIndex());
                    int height = reader.getHeight(reader.getMinIndex());
                    BufferedImage image = create(width, height, imageType);
                    int cores = Math.max(1, Runtime.getRuntime().availableProcessors() / 2);
                    int block = Math.min(MAX_PIXELS_IN_MEMORY / cores / width, (int) (Math.ceil(height / (double) cores)));
                    ExecutorService generalExecutor = Executors.newFixedThreadPool(cores);
                    List<Callable<ImagePartLoader>> partLoaders = new ArrayList<>();
                    for (int y = 0; y < height; y += block) {
                        partLoaders.add(new ImagePartLoader(
                            y, width, Math.min(block, height - y), inputFile, image));
                    }
                    generalExecutor.invokeAll(partLoaders);
                    generalExecutor.shutdown();
                    return image;
                } catch (InterruptedException ex) {
                    Logger.getLogger(BigBufferedImage.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
        return null;
    }

    private static BufferedImage createBigBufferedImage(File tempDir, int width, int height, int imageType)
        throws FileNotFoundException, IOException {
        FileDataBuffer buffer = new FileDataBuffer(tempDir, width * height, 4);
        ColorModel colorModel = null;
        BandedSampleModel sampleModel = null;
        switch (imageType) {
            case TYPE_INT_RGB:
                colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
                    new int[]{8, 8, 8, 0},
                    false,
                    false,
                    ComponentColorModel.TRANSLUCENT,
                    DataBuffer.TYPE_BYTE);
                sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 3);
                break;
            case TYPE_INT_ARGB:
                colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
                    new int[]{8, 8, 8, 8},
                    true,
                    false,
                    ComponentColorModel.TRANSLUCENT,
                    DataBuffer.TYPE_BYTE);
                sampleModel = new BandedSampleModel(DataBuffer.TYPE_BYTE, width, height, 4);
                break;
            default:
                throw new IllegalArgumentException("Unsupported image type: " + imageType);
        }
        SimpleRaster raster = new SimpleRaster(sampleModel, buffer, new Point(0, 0));
        BigBufferedImage image = new BigBufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);
        return image;
    }

    private static class ImagePartLoader implements Callable<ImagePartLoader> {

        private final int y;
        private final BufferedImage image;
        private final Rectangle region;
        private final File file;

        public ImagePartLoader(int y, int width, int height, File file, BufferedImage image) {
            this.y = y;
            this.image = image;
            this.file = file;
            region = new Rectangle(0, y, width, height);
        }

        @Override
        public ImagePartLoader call() throws Exception {
            Thread.currentThread().setPriority((Thread.MIN_PRIORITY + Thread.NORM_PRIORITY) / 2);
            try (ImageInputStream stream = ImageIO.createImageInputStream(file);) {
                Iterator<ImageReader> readers = ImageIO.getImageReaders(stream);
                if (readers.hasNext()) {
                    ImageReader reader = readers.next();
                    reader.setInput(stream, true, true);
                    ImageReadParam param = reader.getDefaultReadParam();
                    param.setSourceRegion(region);
                    BufferedImage part = reader.read(0, param);
                    Raster source = part.getRaster();
                    WritableRaster target = image.getRaster();
                    target.setRect(0, y, source);
                }
            }
            return ImagePartLoader.this;
        }
    }

    private BigBufferedImage(ColorModel cm, SimpleRaster raster, boolean isRasterPremultiplied, Hashtable<?, ?> properties) {
        super(cm, raster, isRasterPremultiplied, properties);
    }

    public void dispose() {
        ((SimpleRaster) getRaster()).dispose();
    }

    public static void dispose(RenderedImage image) {
        if (image instanceof BigBufferedImage) {
            ((BigBufferedImage) image).dispose();
        }
    }

    private static class SimpleRaster extends WritableRaster {

        public SimpleRaster(SampleModel sampleModel, FileDataBuffer dataBuffer, Point origin) {
            super(sampleModel, dataBuffer, origin);
        }

        public void dispose() {
            ((FileDataBuffer) getDataBuffer()).dispose();
        }

    }

    private static final class FileDataBufferDeleterHook extends Thread {

        static {
            Runtime.getRuntime().addShutdownHook(new FileDataBufferDeleterHook());
        }

        private static final HashSet<FileDataBuffer> undisposedBuffers = new HashSet<>();

        @Override
        public void run() {
            final FileDataBuffer[] buffers = undisposedBuffers.toArray(new FileDataBuffer[0]);
            for (FileDataBuffer b : buffers) {
                b.disposeNow();
            }
        }
    }

    private static class FileDataBuffer extends DataBuffer {

        private final String id = "buffer-" + System.currentTimeMillis() + "-" + ((int) (Math.random() * 1000));
        private File dir;
        private String path;
        private File[] files;
        private RandomAccessFile[] accessFiles;
        private MappedByteBuffer[] buffer;

        public FileDataBuffer(File dir, int size) throws FileNotFoundException, IOException {
            super(TYPE_BYTE, size);
            this.dir = dir;
            init();
        }

        public FileDataBuffer(File dir, int size, int numBanks) throws FileNotFoundException, IOException {
            super(TYPE_BYTE, size, numBanks);
            this.dir = dir;
            init();
        }

        private void init() throws FileNotFoundException, IOException {
            FileDataBufferDeleterHook.undisposedBuffers.add(this);
            if (dir == null) {
                dir = new File(".");
            }
            if (!dir.exists()) {
                throw new RuntimeException("FileDataBuffer constructor parameter dir does not exist: " + dir);
            }
            if (!dir.isDirectory()) {
                throw new RuntimeException("FileDataBuffer constructor parameter dir is not a directory: " + dir);
            }
            path = dir.getPath() + "/" + id;
            File subDir = new File(path);
            subDir.mkdir();
            buffer = new MappedByteBuffer[banks];
            accessFiles = new RandomAccessFile[banks];
            files = new File[banks];
            for (int i = 0; i < banks; i++) {
                File file = files[i] = new File(path + "/bank" + i + ".dat");
                final RandomAccessFile randomAccessFile = accessFiles[i] = new RandomAccessFile(file, "rw");
                buffer[i] = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, getSize());
            }
        }

        @Override
        public int getElem(int bank, int i) {
            return buffer[bank].get(i) & 0xff;
        }

        @Override
        public void setElem(int bank, int i, int val) {
            buffer[bank].put(i, (byte) val);
        }

        @Override
        protected void finalize() throws Throwable {
            dispose();
        }

        private void disposeNow() {
            final MappedByteBuffer[] disposedBuffer = this.buffer;
            this.buffer = null;
            disposeNow(disposedBuffer);
        }

        public void dispose() {
            final MappedByteBuffer[] disposedBuffer = this.buffer;
            this.buffer = null;
            new Thread() {
                @Override
                public void run() {
                    disposeNow(disposedBuffer);
                }
            }.start();
        }

        private void disposeNow(final MappedByteBuffer[] disposedBuffer) {
            FileDataBufferDeleterHook.undisposedBuffers.remove(this);
            if (disposedBuffer != null) {
                for (MappedByteBuffer b : disposedBuffer) {
                    ((DirectBuffer) b).cleaner().clean();
                }
            }
            if (accessFiles != null) {
                for (RandomAccessFile file : accessFiles) {
                    try {
                        file.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                accessFiles = null;
            }
            if (files != null) {
                for (File file : files) {
                    file.delete();
                }
                files = null;
            }
            if (path != null) {
                new File(path).delete();
                path = null;
            }
        }

    }
}
Ответил 08/11/2018 в 10:15
источник пользователем

голоса
0

Использование BigBufferedImage вместо BufferedImage. Он хранит изображение на жестком диске , и вам не придется беспокоиться о размере кучи или физического предела памяти . Он может хранить максимум 2147483647 пикселей (или 46,340 х 46340 пикселей).

Создание аз пустого в BigBufferedImage:

    BigBufferedImage image = BigBufferedImage.create(
            tempDir, width, height, type);

Загрузите существующий файл в BigBufferedImage:

    BigBufferedImage image = BigBufferedImage.create(
            imagePath, tempDir, type);

Рендер части изображения:

    part = image.getSubimage(x, y, width, height);

Для получения дополнительной информации о больших обработках изображений прочитать эту статью .

Ответил 18/05/2016 в 11:21
источник пользователем

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more