OutputStream OutOfMemoryError при отправке HTTP

Я пытаюсь опубликовать большой файл видео/изображения из локальной файловой системы по http-пути, но через некоторое время сталкиваюсь с ошибкой нехватки памяти...

вот код

public boolean publishFile(URI publishTo, String localPath) throws Exception {
    InputStream istream = null;
    OutputStream ostream = null;
    boolean isPublishSuccess = false;

    URL url = makeURL(publishTo.getHost(), this.port, publishTo.getPath());
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();


    if (conn != null) {

        try {

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("PUT");
            istream = new FileInputStream(localPath);
            ostream = conn.getOutputStream();

            int n;
            byte[] buf = new byte[4096];
            while ((n = istream.read(buf, 0, buf.length)) > 0) {
                ostream.write(buf, 0, n); //<--- ERROR happens on this line.......???
            }

            int rc = conn.getResponseCode();

            if (rc == 201) {
                isPublishSuccess = true;
            }

        } catch (Exception ex) {
            log.error(ex);
        } finally {
            if (ostream != null) {
                ostream.close();
            }

            if (istream != null) {
                istream.close();
            }
        }
    }

    return isPublishSuccess;

}

Вот ошибка, которую я получаю...

Exception in thread "Thread-8773" java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:2786)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:94)
    at sun.net.www.http.PosterOutputStream.write(PosterOutputStream.java:61)
    at com.test.HTTPClient.publishFile(HTTPClient.java:110)
    at com.test.HttpFileTransport.put(HttpFileTransport.java:97)

person ashchawla    schedule 17.01.2010    source источник
comment
Некоторые (включая меня) считают неприличным кросспостинг: forums.sun.com/thread.jspa ?threadID=5424210 Особенно, когда вы даже не упомянули этот факт.   -  person Joachim Sauer    schedule 17.01.2010
comment
Пожалуйста, не обижайтесь на правку, в которой я критиковал ваш код. Это лучше, чем в среднем, но есть возможности для улучшения. Весь нетривиальный код работает. И легко испортить обработку исключений: я получил заслуженную -1 неделю назад или около того, когда я просто набрал пример try/catch/finally, не дав моему компилятору проверить его.   -  person kdgregory    schedule 18.01.2010


Ответы (6)


HttpUrlConnection буферизует данные, чтобы установить заголовок Content-Length (согласно Спецификация HTTP).

Один из вариантов, если ваш целевой сервер поддерживает это, заключается в использовании "фрагментированные" переводы. Это будет буферизовать только небольшую часть данных за раз. Однако не все сервисы его поддерживают (например, Amazon S3 не поддерживает).

Другая альтернатива (и более лучшая) — использовать Jakarta HttpClient. Вы можете установить «сущность» в запросе из файла, и код подключения установит заголовки запроса соответствующим образом.


Изменить: nos прокомментировал, что OP может вызвать HttpURLConnection.setFixedLengthStreamingMode(long length). Я не знал об этом методе; он был добавлен в 1.5, и с тех пор я не использовал этот класс.

Тем не менее, я по-прежнему предлагаю использовать Jakarta HttpClient по той простой причине, что он уменьшает объем кода, который должен поддерживать OP. Код, который является шаблонным, но все же может содержать ошибки:

  • OP правильно обрабатывает цикл для копирования между вводом и выводом. Обычно, когда я вижу пример этого, постер либо неправильно проверяет возвращаемый размер буфера, либо продолжает перераспределять буферы. Поздравляю, но теперь вы должны позаботиться о том, чтобы ваши преемники проявляли такую ​​же заботу.
  • Обработка исключений не так хороша. Да, OP не забывает закрывать соединения в блоке finally, и еще раз поздравляю с этим. За исключением того, что любой из вызовов close() может вызвать IOException, препятствуя выполнению другого. И метод в целом выбрасывает Exception, так что компилятор не собирается помогать отлавливать подобные ошибки.
  • Я насчитал 31 строку кода для настройки и выполнения ответа (исключая проверку кода ответа и вычисление URL, но включая try/catch/finally). С HttpClient это будет где-то в диапазоне полдюжины LOC.

Даже если ОП написал этот код идеально и преобразовал его в методы, аналогичные тем, что используются в Jakarta Commons IO, он/она не должен этого делать. Этот код был написан и протестирован другими. Я знаю, что переписывать его — пустая трата моего времени, и подозреваю, что это также пустая трата времени ОП.

person kdgregory    schedule 17.01.2010
comment
В этом случае HttpURLConnection.setFixedLengthStreamingMode также может быть вызван в зависимости от размера файла. Это приведет к тому, что HttpUrlConnection вернет прямой поток, поскольку ему не нужно буферизоваться, чтобы определить длину содержимого. - person nos; 17.01.2010
comment
нз правильно. Для этого нет абсолютно никакой необходимости использовать фрагментированный запрос или сторонние библиотеки. Просто вызовите setFixedLengthStreamingMode с длиной файла в HttpURLConnection перед подключением, и getOutputStream вернет небуферизующуюся прямую оболочку в OutputStream сокета. - person jarnbjo; 17.01.2010
comment
@jarnbjo - Я предполагаю, что вы человек, который минусовал меня и всех остальных, кто ответил. И хотя ваши комментарии ценны, было бы более конструктивно опубликовать их в качестве ответа. - person kdgregory; 17.01.2010
comment
kdgregory: Нет смысла повторять решение nos в моем собственном ответе, не так ли? Я не знаю, однако, почему он не написал в ответ единственное разумное решение, позволяющее отдать ему должное. - person jarnbjo; 17.01.2010
comment
@jarnbjo - Ты прав. Вы не смогли добавить ничего конструктивного. Ну, все, что делает вас счастливым. Кстати, я очень впечатлен вашим соотношением голосов «за» и «против». - person kdgregory; 17.01.2010
comment
@kdgregory/skaffman: Если мое соотношение положительных и отрицательных голосов на что-то указывает, так это то, что я нашел здесь большинство ответов довольно бесполезными для исходного постера. Как и в этом случае, однострочное изменение опубликованного кода решит проблему, вместо этого вы оба рекомендуете вместо этого использовать HttpClient. Конечно, это сработает, но это определенно не самое простое решение. - person jarnbjo; 17.01.2010
comment
@jarnbjo - отредактировал сообщение с дополнительным объяснением, которое, надеюсь, даст вам понимание того, почему модификация в одну строку (на самом деле будет две) определенно не самое простое решение. - person kdgregory; 18.01.2010

conn.setFixedLengthStreamingMode((int) new File(localpath).length());

А для буферизации вы можете покрыть свои потоки в BufferedOutputStream и BufferedInputStream.

Хороший пример загрузки по частям вы можете найти здесь: gdata-java-client

person Alexandr    schedule 25.10.2012

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

  1. Увеличьте память для вашего приложения. Вы можете использовать параметр -Xmx1024m, чтобы выделить 1 ГБ памяти для вашего приложения. Это увеличит объем данных, которые вы можете хранить в памяти.

  2. Если у вас по-прежнему не хватает памяти, вы можете попробовать использовать другую библиотеку для загрузки видео, которая не сохраняет все данные в памяти сразу. У Apache Commons HttpClient есть такая функция. Дополнительную информацию см. на этом сайте: http://hc.apache.org/httpclient-3.x/features.html. См. этот раздел для загрузки больших файлов из формы, состоящей из нескольких частей: http://hc.apache.org/httpclient-3.x/methods/multipartpost.html

person Chris Dail    schedule 17.01.2010

Для всего, кроме базовых операций GET, встроенный java.net HTTP-материал не очень хорош. Для этого рекомендуется использовать HttpClient Apache Commons. Это позволяет вам делать гораздо более интуитивно понятные вещи, такие как:

PutMethod put = new PutMethod(url);
put.setRequestEntity(new FileRequestEntity(localFile, contentType));
int responseCode = put.executeMethod();

который заменяет большую часть вашего стандартного кода.

person skaffman    schedule 17.01.2010

HttpsURLConnection#setChunkedStreamingMode(1024 * 1024 * 10); // Фрагмент 10 МБ Это гарантирует, что любой файл (любого размера) будет передаваться через https-соединение без внутренней буферизации. Это следует использовать, когда размер файла или длина содержимого неизвестны.

person Piyush Chordia    schedule 27.04.2015

Ваша проблема в том, что вы пытаетесь исправить X байтов видео в X/N байтов ОЗУ, когда N > 1.

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

Проверьте размер кучи. Вы можете использовать -Xmx, чтобы увеличить его, если вы выбрали значение по умолчанию.

person duffymo    schedule 17.01.2010