Java.lang.OutOfMemoryError: пространство кучи Java при записи в выходной поток

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

private void processFile(Cipher cipher,InputStream inputStream, OutputStream outputStream){
   byte[] tempInputBuffer = new byte[1024];
   int len;
   try {
        while ((len = inputStream.read(tempInputBuffer)) != -1) {
            byte[] tempOutputBuffer = cipher.update(tempInputBuffer, 0, len);
            if ( tempOutputBuffer != null ) outputStream.write(tempOutputBuffer);
        }
        byte[] obuf = cipher.doFinal();
        if ( obuf != null ) outputStream.write(obuf);
        }catch (IOException | IllegalBlockSizeException | BadPaddingException e) {
            e.printStackTrace();    
        }catch (Exception e) {
            e.printStackTrace();        
        }
    }

Это функция, из которой я вызываю processFile

public ByteArrayOutputStream encryptFile( InputStream fileStream, PublicKey publicKey ) throws EmprisException {

        ByteArrayOutputStream encryptedFileStream = null;
        KeyGenerator keyGenerator;
        try {

            //generate AES key
            keyGenerator = KeyGenerator.getInstance(FileUploadDownloadConstants.AES_ALGORITHM);
            keyGenerator.init(128);
            SecretKey secretKey = keyGenerator.generateKey();
            byte[] initializationVector = new byte[16];
            SecureRandom srandom = new SecureRandom();
            srandom.nextBytes(initializationVector);
            IvParameterSpec ivSpec = new IvParameterSpec(initializationVector);

            //encrypting the aes key using rsa public key and adding it to a file
            encryptedFileStream = new ByteArrayOutputStream();
            Cipher cipherRSA = Cipher.getInstance(FileUploadDownloadConstants.RSA_TRANSFORMATION);
            cipherRSA.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] secretKeyBytes = cipherRSA.doFinal(secretKey.getEncoded());
            encryptedFileStream.write(secretKeyBytes);
            encryptedFileStream.write(initializationVector);

            //call processFile to encrypt the file
            Cipher cipherAES = Cipher.getInstance(FileUploadDownloadConstants.AES_TRANSFORMATION);
            cipherAES.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
            processFile(cipherAES, fileStream, encryptedFileStream);
            encryptedFileStream.close();
            logger.logExiting(METHOD_NAME);
            return encryptedFileStream;             
        }catch(Exception e) {
            e.printStackTrace();
        }
    }

Это трассировка стека, которую я получаю:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3236)
    at java.io.ByteArrayOutputStream.grow(ByteArrayOutputStream.java:118)
    at java.io.ByteArrayOutputStream.ensureCapacity(ByteArrayOutputStream.java:93)
    at java.io.ByteArrayOutputStream.write(ByteArrayOutputStream.java:153)
    at java.io.OutputStream.write(OutputStream.java:75)

У меня ошибка нехватки памяти в строке if ( tempOutputBuffer != null ) outputStream.write(tempOutputBuffer);

Что-то не так со способом записи в выходной поток? И это происходит, когда я пытаюсь увеличить файлы, например, около 15 Мб. Буду очень признателен за вашу помощь и заранее спасибо.

Как вы вызываете эту функцию? Какой OutputStream вы передаете

user7 23.04.2018 17:12

На первый взгляд, то, что вы делаете, выглядит правильно с точки зрения реализации потоковой передачи. Но имейте в виду, что ByteArrayOutputStream записывает содержимое внутри кучи. Следовательно, пока вы удерживаете эту переменную, у вас есть большой (например, 15 МБ) фрагмент памяти, который остается в памяти. Это может складываться. Что вы делаете с возвращенным encryptedFileStream, который сохраняет этот фрагмент памяти? А откуда берется InputStream?

GPI 23.04.2018 17:28

Я вижу, что InoutFileStream - это аргумент для encryptFile. Сколько файлов вы открываете одновременно?

Jake 23.04.2018 17:28

Вы пробовали увеличить кучу, как пример это?

Jacob Blanton 23.04.2018 17:32

Ваш код копирует содержимое файла в массив байтов, хранящийся в памяти. Однако 15 Мб - это не так уж и много. Вы обрабатываете много файлов одновременно / параллельно?

lexicore 23.04.2018 19:35

Да, этот код обрабатывает поток только для одного файла за раз.

Aravind S 24.04.2018 05:50

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

Aravind S 24.04.2018 05:54

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

Aravind S 24.04.2018 06:33

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

Jake 24.04.2018 07:54

@AravindS в любом случае возвращает выходной поток не имеет реального смысла (если вызывающий также не записывает в него). Вы должны либо принять выходной поток для записи в качестве аргумента, либо вернуть простой byte []. Увеличение кучи, безусловно, поможет, но с точки зрения программиста необходимо знать, где находится память. Проследите, что такое входной поток и когда он собирается сборщиком мусора, и то же самое для вывода.

GPI 24.04.2018 09:00

@GPI на самом деле интерфейсу нужен выходной поток. Я предполагаю, что ссылки на эти переменные хранятся в куче через цикл, в котором идет процесс шифрования.

Aravind S 24.04.2018 10:19

@AravindS у вас может не быть шанса, кроме как выполнить то, что просит "интерфейс". Но каковы методы OutputStream? write(). Что может делать фронтенд с write? Ничего, потому что, конечно, он не перезаписывает / после процесса шифрования. Так что с вероятностью 99% то, что он делает, - это toByteArray. Это означает, что на самом деле фронт хочет байтов, а не потоков. Возврат ByteArrayOutputStream - это своего рода запах кода: 1) потому что это реализация, а не интерфейс, 2) потому что возвращение outputStream имеет смысл только тогда, когда вы хотите вызвать write(). А фронтенд, я уверен, не делает.

GPI 24.04.2018 10:29

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

Aravind S 24.04.2018 10:38
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
13
2 780
1

Ответы 1

Вы пробовали установить минимальный размер кучи для вашей JVM? См. Этот связанный вопрос:

Каковы параметры Xms и Xmx при запуске JVM?

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

Да, я только что исследовал это, но я пытаюсь запустить эту программу, максимально не изменяя конфигурации.

Aravind S 24.04.2018 06:56

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

Greg Brown 24.04.2018 13:49

Другие вопросы по теме