Загрузка больших файлов через веб-сервис на Java
Вопрос
У меня есть веб-приложение, которое является хранилищем файлов.Это веб-приложение предоставляет веб-службы, которые позволяют клиентам выполнять поиск в репозитории и загружать любые вложения через SOAP.
В настоящее время я пытаюсь использовать Spring-WS 1.5.8 с MTOM для отправки вложения клиенту, но у меня постоянно возникают ошибки из-за нехватки памяти.Я не думаю, что эти ошибки связаны с моим экземпляром Tomcat 6, поскольку на моем сервере 8 ГБ памяти, и я настроил Tomcat на использование 4 ГБ из нее.Я получаю эти ошибки для файлов размером до 200 МБ.
Мне нужно использовать SOAP, хотя это, вероятно, совсем не лучший подход.Я бы предпочел решение весной, но если это невозможно, я открыт для других идей.Я читал, что можно использовать AxiomSoapMessageFactory для потоковой передачи файлов на сервер для загрузки, но не наоборот.Это правда?Я использую Java 6.
Вот ошибка, которую я постоянно получаю в Spring WS Framework:
java.lang.OutOfMemoryError: Java heap space
com.sun.xml.internal.messaging.saaj.util.ByteOutputStream.ensureCapacity(Unknown Source)
com.sun.xml.internal.messaging.saaj.util.ByteOutputStream.write(Unknown Source)
com.sun.xml.internal.messaging.saaj.packaging.mime.internet.BMMimeMultipart.find(Unknown Source)
com.sun.xml.internal.messaging.saaj.packaging.mime.internet.BMMimeMultipart.readBody(Unknown Source)
com.sun.xml.internal.messaging.saaj.packaging.mime.internet.BMMimeMultipart.getNextPart(Unknown Source)
com.sun.xml.internal.messaging.saaj.packaging.mime.internet.BMMimeMultipart.parse(Unknown Source)
com.sun.xml.internal.messaging.saaj.packaging.mime.internet.BMMimeMultipart.parse(Unknown Source)
com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimeMultipart.getCount(Unknown Source)
com.sun.xml.internal.messaging.saaj.soap.MessageImpl.initializeAllAttachments(Unknown Source)
com.sun.xml.internal.messaging.saaj.soap.MessageImpl.getAttachments(Unknown Source)
org.springframework.ws.soap.saaj.Saaj13Implementation.getAttachment(Saaj13Implementation.java:305)
org.springframework.ws.soap.saaj.SaajSoapMessage.getAttachment(SaajSoapMessage.java:226)
org.springframework.ws.support.MarshallingUtils$MimeMessageContainer.getAttachment(MarshallingUtils.java:109)
org.springframework.oxm.jaxb.Jaxb2Marshaller$Jaxb2AttachmentUnmarshaller.getAttachmentAsDataHandler(Jaxb2Marshaller.java:532)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.MTOMDecorator.startElement(Unknown Source)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.InterningXmlVisitor.startElement(Unknown Source)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.SAXConnector.startElement(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.visit(Unknown Source)
com.sun.xml.internal.bind.unmarshaller.DOMScanner.scan(Unknown Source)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal0(Unknown Source)
com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallerImpl.unmarshal(Unknown Source)
javax.xml.bind.helpers.AbstractUnmarshallerImpl.unmarshal(Unknown Source)
org.springframework.oxm.jaxb.Jaxb2Marshaller.unmarshal(Jaxb2Marshaller.java:421)
org.springframework.ws.support.MarshallingUtils.unmarshal(MarshallingUtils.java:62)
org.springframework.ws.client.core.WebServiceTemplate$3.extractData(WebServiceTemplate.java:374)
org.springframework.ws.client.core.WebServiceTemplate.doSendAndReceive(WebServiceTemplate.java:560)
Решение
Это может быть связано с тем, что пространство вашего Эдема слишком мало.Пространство Эдема — это часть кучи, в которой размещаются новые объекты и остаются до тех пор, пока они не переживут сборку мусора.Пространство Эдема не очень большое.(у меня нет значения по умолчанию, но при настройке по умолчанию с кучей 1 ГБ это всего 64 МБ)
Ваш файл, вероятно, будет загружен в пространство Eden.Либо нет 200 МБ свободного места, либо массив байтов выделен мал и его нужно увеличивать.Единственный способ создания массива в Java — это выделить новый массив большего размера и выполнить копирование в память.Это будет. Рост со 100 МБ до 200 МБ, очевидно, требует 300 МБ общего пространства кучи Eden.
Вы можете попробовать установить -XX:NewSize=4196M
который выделит 4 ГБ пространства кучи Eden.
Я должен сказать, что я не знаю, работает ли Tomcat в каком-то серверном режиме, который использует другую стратегию GC/кучи.
Вы можете использоватьvisualgc
от jvmstat 3.0 (не дистрибутив, входящий в состав Java 5 и 6), чтобы контролировать кучу и определять, какое пространство кучи заполнено.
Вы также можете проверить: Настройка сборки мусора с помощью виртуальной машины Java[tm] версии 5.0
Если вы решите эту проблему, вы все равно столкнетесь с низкой производительностью и невозможностью масштабирования.Вам, вероятно, будет лучше с какой-нибудь прямой потоковой передачей.Для этой цели не должно быть сложно реализовать простой сервлет.
Другие советы
SOAP/XML в Java всегда требует много накладных расходов и требует много памяти.В этом конкретном случае он пытается выделить (слишком большой) byte[] в памяти вместо того, чтобы записывать поток непосредственно в другой тип OutputStream (любой, кроме ByteArrayOutputStream).
Вы решили вообще забыть об интерфейсе SOAP и вернуться к основам, используя java.net.URLConnection, и развивать это дальше?Таким образом, вы можете записать InputStream непосредственно на диск, используя FileOutputStream, что намного эффективнее, чем хранить все это в памяти.
Похоже, вы обрабатываете весь файл в памяти, а не читаете его при отправке клиенту.
Можете ли вы вместо этого перенести это на веб-сервер, если вы создадите URL-адрес, который разрешается для фактического файла для отправки, и оставите его ему?