Запись Java в утечку памяти ByteArrayOutputStream

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

ImageIO.write(image, "gif", byteArray);

Память увеличивается ОЧЕНЬ сильно, какая-то утечка памяти.

я отправляю с помощью этого

ImageIO.write(image, "gif", byteArrayO);         
byte [] byteArray = byteArrayO.toByteArray();
byteArrayO.flush();
byteArrayO.reset();
Connection.pw.println("" + byteArray.length);
int old = Connection.client.getSendBufferSize();
Connection.client.setSendBufferSize(byteArray.length);
Connection.client.getOutputStream().write(byteArray, 0, byteArray.length);
Connection.client.getOutputStream().flush();
image.flush();
image = null;
byteArrayO = null;
byteArray = null;
System.gc();
Connection.client.setSendBufferSize(old);

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

В любом случае я могу очистить byteArray и удалить все, что в нем есть, из памяти? Я знаю, что reset() есть, но здесь его нет. Я хочу сразу избавиться от ByteArrayOutputStream, когда это будет сделано.


person Wille Sandström    schedule 15.08.2012    source источник
comment
Какими свойствами обладает image? Размеры, цветовая модель и т.д.?   -  person lost    schedule 15.08.2012
comment
с каким объемом памяти вы работаете? ваши настройки ВМ? и какой размер изображения?   -  person Ravi Bhatt    schedule 15.08.2012
comment
Вы действительно получаете OutOfMemoryError на ImageIO.write(...)?   -  person Christoffer Hammarström    schedule 15.08.2012
comment
Задавая один и тот же вопрос over и over с одной и той же информацией вряд ли даст вам лучшие ответы и потратит время всех. Если вам нужны реальные ответы, опубликуйте свою программу целиком, чтобы любой, кто хочет помочь, мог скомпилировать ее и запустить. Если ваша программа слишком велика, чтобы опубликовать ее целиком, сократите ее до тех пор, пока вы не сможете опубликовать ее и по-прежнему показывать ее поведение.   -  person kdgregory    schedule 15.08.2012
comment
Похоже, вы действительно спрашиваете: почему так сильно увеличивается память java и что я могу сделать, чтобы этого избежать?   -  person Sarel Botha    schedule 15.08.2012
comment
Написание ошибки происходит без предоставления фактической трассировки стека, по крайней мере, бесполезно...   -  person Nikem    schedule 16.08.2012


Ответы (5)


@Christoffer Hammarström, вероятно, имеет лучшее решение, но я добавлю его, чтобы попытаться объяснить использование памяти.

Эти 2 строки создают 3 копии ваших данных изображения:

ImageIO.write(image, "gif", byteArrayO);
byte [] byteArray = byteArrayO.toByteArray(); 

После выполнения этого у вас есть одна копия данных, хранящихся в изображении, одна копия в ByteArrayOutputStream и другая копия в массиве байтов (toByteArray() не возвращает внутренний буфер, он создает копию).

Вызов reset() не освобождает память внутри ByteArrayOutputStream, он просто сбрасывает счетчик позиций обратно в 0. Данные все еще там.

Чтобы память могла быть освобождена раньше, вы можете назначить каждому элементу значение null, как только вы закончите с ним. Это позволит собирать память сборщику мусора, если он решит запуститься раньше. НАПРИМЕР:

ImageIO.write(image, "gif", byteArrayO);
image = null;
byte [] byteArray = byteArrayO.toByteArray(); 
byteArrayO = null;
...
person Aaron    schedule 15.08.2012

Зачем вам возиться с размером буфера отправки? Какой протокол вы используете поверх этого сокета? Это должно быть так же просто, как:

ImageIO.write(image, "gif", Connection.client.getOutputStream());

Если вам нужно использовать ByteArrayOutputStream, по крайней мере, используйте

byteArrayO.writeTo(Connection.client.getOutputStream())

так что вы не делаете лишний избыточный byte[].

person Christoffer Hammarström    schedule 15.08.2012

Это не совсем тот ответ, который вам нужен, но то, что вы, возможно, захотите рассмотреть.

Почему бы не создать пул массивов байтов и не использовать их повторно каждый раз, когда вам это нужно. Это будет немного эффективнее, так как вы не будете создавать новые массивы и постоянно их выбрасывать. Использование меньшего количества gc всегда хорошо. Вы также сможете гарантировать, что приложению достаточно памяти для постоянной работы.

person theINtoy    schedule 15.08.2012

Вы можете запросить виртуальную машину для запуска сборки мусора через System.gc(), но это НЕ гарантируется. Виртуальная машина выполняет сборку мусора, когда сочтет это необходимым или подходящим моментом.

person Kevin Mangold    schedule 15.08.2012
comment
Я делаю в конце моего кода. Не поможет. Я распоряжаюсь и обнуляю объект и все такое, но он все еще остается в памяти. - person Wille Sandström; 15.08.2012
comment
То, что вы избавляетесь от нее, не означает, что память немедленно освобождается. Виртуальная машина сама решает, когда освобождать память. - person Kevin Mangold; 15.08.2012

То, что вы описываете, вполне нормально. Он должен куда-то поместить байты изображения, которое вы создаете.

Вместо памяти вы можете использовать FileOutputStream для записи байтов. Затем вам нужно создать FileInputStream для чтения из файла, в который вы написали, и цикл, который считывает байты в буфер массива байтов размером, скажем, 64 КБ, а затем записывает эти байты в выходной поток соединения.

Вы упоминаете ошибку. Если вы получаете сообщение об ошибке, что это за ошибка?

Если вы используете клиентскую JVM (аргумент -client для java), тогда память может быть возвращена ОС, и процесс Java снова сократится. Я не уверен в этом.

Если вам не нравится, сколько памяти использует JAI, вы можете попробовать использовать Sanselan: http://commons.apache.org/imaging/

person Sarel Botha    schedule 15.08.2012