Java Heap Space (CMS с огромными файлами) - PullRequest
2 голосов
/ 16 июня 2009

EDIT:

Получил каталог, чтобы жить. Теперь есть еще одна проблема:

Файлы в хранилище сохраняются с идентификатором БД в качестве префикса. к их именам файлов. Конечно, я не хочу, чтобы пользователи видели их.

Есть ли способ объединить response.redirect и настройку заголовка für имя файла и размер?

лучший

     A

Привет снова,

новый подход:

Можно ли создать виртуальный каталог, подобный IIS, в tomcat по порядку избежать потоковой передачи и использовать только перенаправление заголовка? Я играл с контексты, но не могли это сделать ...

есть идеи?

    thx

A * * тысяча двадцать-один

Привет%,

Я столкнулся с проблемой проводной связи с пространством кучи Java, которое близко чтобы привести меня к канатам.

Короткая версия:

Я написал ContentManagementSystem, которая должна обрабатывать огромные файлы (> 600 МБ) тоже. Настройки кучи Tomcat:

-Xmx700m -Xms400m

Проблема в том, что загрузка огромных файлов работает, хотя это медленный. Загрузка файлов приводит к исключению пространства кучи Java.

Попытка загрузить файл 370 Мб заставляет tomcat перейти на кучу 500 Мб (что должно быть в порядке) и заканчивается исключением пространства кучи Java.

Не понимаю, почему загрузка работает, а загрузка нет? Вот мой код загрузки:

byte[] byt = new byte[1024*1024*2];

response.setHeader("Content-Disposition", "attachment;filename=\"" + fileName + "\""); 

FileInputStream fis = null;
OutputStream os = null;

fis = new FileInputStream(new File(filePath));
os = response.getOutputStream();

BufferedInputStream buffRead = new BufferedInputStream(fis);

while((read = buffRead.read(byt))>0)            
{
    os.write(byt,0,read);
    os.flush();
}

buffRead.close();
os.close();

Если я правильно понял, буферизованный читатель должен позаботиться о любом проблема с памятью, верно?

Любая помощь будет высоко оценена, так как у меня закончились идеи

С уважением,

W

Ответы [ 9 ]

5 голосов
/ 16 июня 2009

Если я правильно понял, буфер читатель должен заботиться о любой памяти вопрос, верно?

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

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

Что возвращает response.getBufferSize()? И вы должны попробовать установить response.setContentLength() для размера файла; Я смутно помню, что веб-контейнер при определенных обстоятельствах буферизирует весь ответ, чтобы определить длину контента, так что, возможно, именно это и происходит. В любом случае, это хорошая практика, так как это позволяет клиентам отображать размер загрузки и указывать ETA для загрузки.

1 голос
/ 16 июня 2009

Требуется ли обслуживать файлы с помощью Tomcat? Для такого рода задач мы использовали отдельный механизм загрузки. Мы подключили Apache -> Tomcat -> хранилище, а затем добавили правила перезаписи для загрузки. Тогда вы просто обойдете Tomcat, и Apache передаст файл клиенту (Apache-> storage). Но если работает, только если у вас есть файлы, хранящиеся в виде файлов. Если вы читаете из БД или другого типа не файлового хранилища, это решение не может быть успешно использовано. общий сценарий состоит в том, что вы генерируете ссылки для загрузки файлов, например, domain / binaries / xyz ... и напишите правило перенаправления для домена / файлов, используя Apache mod_rewrite.

1 голос
/ 16 июня 2009

Следующий код может передавать данные клиенту, выделяя только небольшой буфер (BUFFER_SIZE, это мягкая точка, поскольку вы можете настроить его):

private static final int OUTPUT_SIZE = 1024 * 1024 * 50; // 50 Mb
private static final int BUFFER_SIZE = 4096;

@Override
protected void doGet(HttpServletRequest request,HttpServletResponse response) 
                     throws ServletException, IOException {
    String fileName = "42.txt";

    // build response headers
    response.setStatus(200);
    response.setContentLength(OUTPUT_SIZE);
    response.setContentType("text/plain");
    response.setHeader("Content-Disposition", 
                        "attachment;filename=\"" + fileName + "\"");
    response.flushBuffer(); // write HTTP headers to the client

    // streaming result
    InputStream fileInputStream = new InputStream() { // fake input stream
        int i = 0;

        @Override
        public int read() throws IOException {
            if (i++ < OUTPUT_SIZE) {
                return 42;
            } else {
                return -1;
            }
        }
    };

    ReadableByteChannel input = Channels.newChannel(fileInputStream);
    WritableByteChannel output = Channels.newChannel(
                                    response.getOutputStream());
    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);

    while (input.read(buffer) != -1) {
        buffer.flip();
        output.write(buffer);
        buffer.clear();
    }

    input.close();
    output.close();
}
1 голос
/ 16 июня 2009

Мои предложения:

Quick-n-easy: Используйте меньший массив! Да, это больше зацикливается, но это не будет проблемой. 5 килобайт это просто отлично. Вы узнаете, будет ли это работать адекватно для вас через несколько минут.

byte[] byt = new byte[1024*5];

Немного сложнее: Если у вас есть доступ к sendfile (как в Tomcat с документацией Http11NioProtocol - здесь ), используйте его

Немного сложнее, опять же: Переключите ваш код на FileChannel Java NIO. У меня очень, очень похожий код, работающий с одинаково большими файлами с сотнями одновременных подключений и схожими настройками памяти без проблем. В этих ситуациях NIO быстрее, чем простые старые потоки Java. Он использует магию DMA ( Прямой доступ к памяти ), позволяющую передавать данные с диска на сетевую карту, даже не проходя через ОЗУ или ЦП. Вот фрагмент кода для моей собственной кодовой базы ... Я много разобрал, чтобы показать основы. FileChannel.transferTo () не гарантирует отправку каждого байта, поэтому он находится в этом цикле.

WritableByteChannel destination = Channels.newChannel(response.getOutputStream());
FileChannel         source      = file.getFileInputStream().getChannel();

while (total < length) {
    long sent = source.transferTo(start + total, length - total, destination);
    total += sent;
}
1 голос
/ 16 июня 2009

Для этого лучше использовать java.nio , чтобы вы могли читать ресурсы частично, а свободные ресурсы уже транслировались!

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

1 голос
/ 16 июня 2009

Попробуйте использовать setBufferSize и flushBuffer методов ServletResponse.

0 голосов
/ 16 июня 2009

2-Мбайтный буфер в порядке слишком большой! Несколько k должно быть достаточно. Объекты размером в мегабайты представляют собой реальную проблему для сборщика мусора, поскольку их часто необходимо обрабатывать отдельно от «обычных» объектов (нормальный == намного меньше, чем поколение кучи). Чтобы оптимизировать ввод-вывод, ваш буфер должен быть лишь немного больше размера буфера ввода-вывода, т. Е. Как минимум такого же размера, как дисковый блок или сетевой пакет.

0 голосов
/ 16 июня 2009

Почему бы вам не использовать собственный FileServlet от Tomcat?

Он может выдавать файлы намного лучше, чем вы можете себе представить.

0 голосов
/ 16 июня 2009

У вас есть какие-либо фильтры в приложении или вы используете библиотеку tcnative? Вы можете попробовать профилировать его с помощью jvisualvm?

Редактировать : Небольшое замечание: обратите внимание, что у вас есть возможность атаки с разделением ответов HTTP в setHeader, если вы не очищаете fileName.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...