OOM при попытке обработать файл s3 - PullRequest
0 голосов
/ 08 ноября 2019

Я пытаюсь использовать приведенный ниже код для загрузки и чтения данных из файла, независимо от того, как это происходит OOM, точно при чтении файла, размер файла s3 составляет 22 МБ, я загрузил через браузер это 650 МБ, но когда яМониторинг через визуальную виртуальную машину, потребление памяти при распаковке и чтении составляет более 2 ГБ. Кто-нибудь, пожалуйста, руководство, чтобы я нашел причину высокого использования памяти. Спасибо.

public static String unzip(InputStream in) throws IOException, CompressorException, ArchiveException {
            System.out.println("Unzipping.............");
            GZIPInputStream gzis = null;
            try {
                gzis = new GZIPInputStream(in);
                InputStreamReader reader = new InputStreamReader(gzis);
                BufferedReader br = new BufferedReader(reader);
                double mb = 0;
                String readed;
                int i=0;
                while ((readed = br.readLine()) != null) {
                     mb = mb+readed.getBytes().length / (1024*1024);
                     i++;
                     if(i%100==0) {System.out.println(mb);}
                }


            } catch (IOException e) {
                e.printStackTrace();
                LOG.error("Invoked AWSUtils getS3Content : json ", e);
            } finally {
                closeStreams(gzis, in);
            }

Исключение в потоке "main" java.lang.OutOfMemoryError: пространство кучи Java в java.util.Arrays.copyOf (Arrays.java:3332) в java.lang.AbstractStringBuilder.ensureCapacityInternal (AbstractStringBuilder.java:124) в java.lang.AbstractStringBuilder.append (AbstractStringBuilder.java:596) в java.lang.StringBuffer.append (StringBuffer.java:367) в java.read.BuderedReader (Buffered Reader. java: 370) по адресу java.io.BufferedReader.readLine (BufferedReader.java:389) по адресу com.kpmg.rrf.utils.AWSUtils.unzip (AWSUtils.java:917)

Monitoring

Ответы [ 2 ]

1 голос
/ 08 ноября 2019

Это теория, но я не могу придумать каких-либо других причин, по которым ваш пример мог бы привести OOM.

Предположим, что несжатый файл состоит из очень длинной строки;например, что-то вроде 650 миллионов ASCII-байтов.

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

Внутри метод readLine() читает символы по одному и добавляет их к StringBuffer. (Вы можете увидеть вызов append в трассировке стека.) Если файл состоит из очень большой строки, то StringBuffer станет очень большим.

  • Каждыйтекстовый символ в несжатой строке становится char в char[], который является буферной частью StringBuffer.

  • Каждый раз, когда буфер заполняется, StringBuffer будетувеличить буфер путем (я думаю) удвоения его размера. Это влечет за собой выделение нового char[] и копирование в него символов.

  • Таким образом, если буфер заполняется при наличии N символов, Arrays.copyOf выделит char[] hold 2 xN символов. И в то время как данные копируются, в общей сложности будет использовано 3 x N символьного хранилища.

  • Таким образом, 650 МБ может легко превратиться в требование кучи> 6 x 650M байт

Следует также отметить, что массив 2 x N должен представлять собой один непрерывный узел кучи.

Глядя на графики кучи, похоже, что куча достигла ~ 1 ГБ. Если моя теория верна, следующее распределение было бы для узла ~ 2 ГБ. Но 1 ГБ + 2 ГБ прямо на пределе для вашей кучи 3.1 ГБ макс. И когда мы принимаем во внимание требование смежности, распределение не может быть выполнено.


Так, каково решение?

Это действительно просто: не используйте readLine(), еслистроки могут быть неоправданно длинными.

    public static String unzip(InputStream in) 
            throws IOException, CompressorException, ArchiveException {
        System.out.println("Unzipping.............");
        try (
            GZIPInputStream gzis = new GZIPInputStream(in);
            InputStreamReader reader = new InputStreamReader(gzis);
            BufferedReader br = new BufferedReader(reader);
        ) {
            int ch;
            long i = 0;
            while ((ch = br.read()) >= 0) {
                 i++;
                 if (i % (100 * 1024 * 1024) == 0) {
                     System.out.println(i / (1024 * 1024));
                 }
            }
        } catch (IOException e) {
            e.printStackTrace();
            LOG.error("Invoked AWSUtils getS3Content : json ", e);
        }
0 голосов
/ 08 ноября 2019

Я тоже подумал о слишком длинной очереди. Подумав еще, я думаю, что StringBuffer, который используется внутри JVM, должен быть преобразован в тип результата readline: String. Строки являются неизменяемыми, но по соображениям скорости JVM даже не будет искать, если строка дублируется. Таким образом, он может распределять строку много раз, в конечном итоге заполняя кучу уже не используемыми фрагментами строки.

Я бы рекомендовал не читать строки или символы, а куски байтов. Байт [] размещается в куче и может быть впоследствии отброшен. Конечно, вы будете считать байты вместо символов. Если вы не знаете разницу и нуждаетесь в символах, которые могли бы быть более стабильным и производительным решением.

Этот код просто пишется памятью и не тестируется:

public static String unzip(InputStream in) 
            throws IOException, CompressorException, ArchiveException {
        System.out.println("Unzipping.............");
        try (
            GZIPInputStream gzis = new GZIPInputStream(in);
        ) {
            byte[] buffer = new byte[8192];
            long i = 0;
            int read = gzis.read(buffer);
            while (read >= 0) {
                 i+=read;
                 if (i % (100 * 1024 * 1024) == 0) {
                     System.out.println(i / (1024 * 1024));
                 }
                 read = gzis.read(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
            LOG.error("Invoked AWSUtils getS3Content : json ", e);
        }```
...