Минимизировать влияние на память при создании и загрузке файла - PullRequest
0 голосов
/ 29 мая 2020

Только для целей тестирования я создал этот контроллер, который генерирует файл размером 100 МБ и возвращает его клиенту. Метод очень быстрый. Содержание файла не имеет значения. Файл генерируется на лету, на диск он не сохраняется.

Можно ли уменьшить нагрузку на память, особенно на java heap? Спасибо

    @GetMapping("/testDownload100MB")
    public ResponseEntity<Resource> download100MB() throws IOException {

        int sizeInBytes = 100 * 1024 * 1024;

        ByteArrayOutputStream outStream = new ByteArrayOutputStream(sizeInBytes);
        for (int i = 0; i < sizeInBytes; i++) {
            outStream.write(0);
        }
        Resource resource = new ByteArrayResource(outStream.toByteArray());

        return ResponseEntity.ok()
                .headers(utilities.getGenericHttpHeadersToDownloadFile())
                .contentLength(sizeInBytes)
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(resource);
    }

Код utilities.getGenericHttpHeadersToDownloadFile() не имеет отношения к вопросу, но я сообщаю об этом для полноты:

    public HttpHeaders getGenericHttpHeadersToDownloadFile() {
        HttpHeaders headers = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=myfile");
        headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
        headers.add("Pragma", "no-cache");
        headers.add("Expires", "0");
        return headers;
    }

1 Ответ

0 голосов
/ 29 мая 2020

Решено.

Код в моем вопросе выдает java.lang.OutOfMemoryError: Java heap space на моем сервере Spring Boot. Этот новый код решает проблему:

    @GetMapping("/testDownload100MB")
    public ResponseEntity<Resource> download100MB() throws IOException {

        int size1MB = 1 * 1024 * 1024; // size in bytes

        File tempFile = new File(System.getProperty("user.home") + File.separator + "tempFile" + System.currentTimeMillis());

        for (int i = 0; i < 100; i++) {
            ByteArrayOutputStream outStream = new ByteArrayOutputStream(size1MB);
            for (int j = 0; j < size1MB; j++) {
                outStream.write(0);
            }
            FileUtils.writeByteArrayToFile(tempFile, outStream.toByteArray(), true); // append to the temp file
        }

        Resource resource = new FileSystemResource(tempFile);
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                // we can assume that the file can be safely deleted after five minutes
               tempFile.delete();
            }
        }, 1000 * 60 * 5);


        return ResponseEntity.ok()
                .headers(utilities.getGenericHttpHeadersToDownloadFile())
                .contentLength(tempFile.length())
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(resource);
    }

Обратите внимание, что FileUtils необходимо импортировать с помощью: import org.apache.commons.io.FileUtils;.

В этом случае объекты размером более 1 МБ не помещаются в java куча. Кроме того, FileSystemResource не нужно загружать весь файл в память. С другой стороны, мне пришлось добавить таймер для удаления файла: поскольку это метод для целей тестирования, я могу с уверенностью предположить, что через несколько минут файл больше не нужен.

Если есть лучшие решения, которые не требуют временного файла, добавьте свой ответ :)

...