Я получил OutOfMemoryError, когда попытался загрузить большой файл (> 10 ГБ) с помощью JDK HttpClient. Это произошло только тогда, когда я использовал BodyHandlers.ofInputStream. Если я использую BodyHandlers.ofFile, ошибок не было. Я использую JDK 11. Кто-нибудь знает, как это решить? Мне нужен InputStream для работы.
Выбрасывается OutOfMemoryError:
HttpResponse<InputStream> httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream());
try (InputStream responseStream = httpResponse.body()) {
Files.copy(responseStream, outputFile);
}
Ошибка не возникает:
HttpResponse<Path> httpResponse = httpClient.send(request, HttpResponse.BodyHandlers.ofFile(outputFile));
Вот трассировка стека.
java.io.IOException: closed
at jdk.internal.net.http.ResponseSubscribers$HttpResponseInputStream.current(ResponseSubscribers.java:342) ~[java.net.http:?]
at jdk.internal.net.http.ResponseSubscribers$HttpResponseInputStream.read(ResponseSubscribers.java:393) ~[java.net.http:?]
at java.io.InputStream.transferTo(InputStream.java:704) ~[?:?]
at java.nio.file.Files.copy(Files.java:3078) ~[?:?]
at <Masked for privacy>
at <Masked for privacy>
at <Masked for privacy>
Caused by: java.lang.OutOfMemoryError: Java heap space
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:937) ~[?:?]
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:491) ~[?:?]
at javax.crypto.CipherSpi.bufferCrypt(CipherSpi.java:779) ~[?:?]
at javax.crypto.CipherSpi.engineDoFinal(CipherSpi.java:730) ~[?:?]
at javax.crypto.Cipher.doFinal(Cipher.java:2497) ~[?:?]
at sun.security.ssl.SSLCipher$T12GcmReadCipherGenerator$GcmReadCipher.decrypt(SSLCipher.java:1629) ~[?:?]
at sun.security.ssl.SSLEngineInputRecord.decodeInputRecord(SSLEngineInputRecord.java:240) ~[?:?]
at sun.security.ssl.SSLEngineInputRecord.decode(SSLEngineInputRecord.java:197) ~[?:?]
at sun.security.ssl.SSLEngineInputRecord.decode(SSLEngineInputRecord.java:160) ~[?:?]
at sun.security.ssl.SSLTransport.decode(SSLTransport.java:110) ~[?:?]
at sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:681) ~[?:?]
at sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:636) ~[?:?]
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:454) ~[?:?]
at sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:433) ~[?:?]
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:637) ~[?:?]
at jdk.internal.net.http.common.SSLFlowDelegate$Reader.unwrapBuffer(SSLFlowDelegate.java:481) ~[java.net.http:?]
at jdk.internal.net.http.common.SSLFlowDelegate$Reader.processData(SSLFlowDelegate.java:392) ~[java.net.http:?]
at jdk.internal.net.http.common.SSLFlowDelegate$Reader$ReaderDownstreamPusher.run(SSLFlowDelegate.java:264) ~[java.net.http:?]
at jdk.internal.net.http.common.SequentialScheduler$SynchronizedRestartableTask.run(SequentialScheduler.java:175) ~[java.net.http:?]
at jdk.internal.net.http.common.SequentialScheduler$CompleteRestartableTask.run(SequentialScheduler.java:147) ~[java.net.http:?]
at jdk.internal.net.http.common.SequentialScheduler$SchedulableTask.run(SequentialScheduler.java:198) ~[java.net.http:?]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128) ~[?:?]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628) ~[?:?]
at java.lang.Thread.run(Thread.java:834) ~[?:?]
Что вам рассказала свалка кучи? (Насколько велика была куча? Что потребляло память?)
Мне пришлось бы предположить, поскольку я не знаю подробностей этих классов, но я предполагаю, что обработчик, возвращаемый ofFile(), записывает данные на диск (согласно JavaDoc), а обработчик ofInputStream() хранит данные в памяти. Если так, то это объяснит вам OOM. Как предлагает pratap, сначала запишите данные в файл, а затем прочтите их оттуда - либо по частям, либо с помощью файлов с отображением в память.
@meriton Files.copy использует только 8 КБ в качестве буфера, поэтому он должен быть где-то еще для кэширования данных. Думаю, это должно быть связано с крипто-классами.
@pratap Это последнее средство, если я не могу его решить.
BodyHandler, возвращаемый ofInputStream(), будет хранить некоторые данные в памяти, но не должен хранить все 10 ГБ ... Какую версию (java --version) JDK 11 вы используете? Проблема воспроизводится в более поздней версии (JDK 16)?
Также знаете ли вы, использовались ли HTTP / 1.1 или HTTP / 2?
@daniel Я пробовал использовать Java 16, и ошибки не было. Он использует HTTP / 1.1. В любом случае я сначала сохраню весь файл на диск с помощью ofFile. Но я думаю, что fromSubscriber(Flow.Subscriber<? super List<ByteBuffer>>) может решить проблему, потому что ofFile его использует. Однако сейчас у меня нет времени, чтобы попробовать это.
Я ошибался. Та же проблема возникает в Java 16.
Это странно. Было бы полезно получить дамп кучи и посмотреть, какие ресурсы буферизуются и откуда.




было бы лучше, если бы вы загрузили весь ответ в файл, разбили его на части, а затем обработали эти фрагменты с помощью входного потока.