Ошибка отладки Java из памяти - PullRequest
4 голосов
/ 09 февраля 2012

Я все еще относительно новый программист, и проблема, с которой я постоянно сталкиваюсь в Java, - это «Недостаточно памяти». Я не хочу увеличивать память с помощью -Xmx, потому что чувствую, что ошибка связана с плохим программированием, и я хочу улучшить свое кодирование, а не полагаться на больше памяти.

Работа, которую я выполняю, включает обработку большого количества текстовых файлов, каждый размером около 1 ГБ при сжатии. Приведенный здесь код предназначен для циклического перемещения по каталогу, в который сбрасываются новые сжатые текстовые файлы. Он открывает второй самый последний текстовый файл (не самый последний, потому что он все еще записывается) и использует библиотеку Jsoup для анализа определенных полей в текстовом файле (поля разделяются пользовательскими разделителями: «| nTa |» обозначает новый столбец и "| nLa |" обозначает новую строку).

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

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

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
import java.util.TreeMap;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

import org.jsoup.Jsoup;

public class ParseHTML {

    public static int commentExtractField = 3;
    public static int contentExtractField = 4;
    public static int descriptionField = 5;

    public static void main(String[] args) throws Exception {

        File directoryCompleted = null;     
        File filesCompleted[] = null;

        while(true) {

            // find second most recent file in completed directory  
            directoryCompleted = new File(args[0]);     
            filesCompleted = directoryCompleted.listFiles();

            if (filesCompleted.length > 1) {

                TreeMap<Long, File> timeStamps = new TreeMap<Long, File>(Collections.reverseOrder());

                for (File f : filesCompleted) {
                    timeStamps.put(getTimestamp(f), f);
                }

                File fileToProcess = null;

                int counter = 0;

                for (Long l : timeStamps.keySet()) {
                    fileToProcess = timeStamps.get(l);
                    if (counter == 1) {
                        break;
                    }
                    counter++;
                }   

                // start processing file
                GZIPInputStream gzipInputStream = null;

                if (fileToProcess != null) {
                    gzipInputStream = new GZIPInputStream(new FileInputStream(fileToProcess));
                }

                else {
                    System.err.println("No file to process!");
                    System.exit(1);
                }

                Scanner scanner = new Scanner(gzipInputStream);
                scanner.useDelimiter("\\|nLa\\|");

                GZIPOutputStream output = new GZIPOutputStream(new FileOutputStream("parsed/" + fileToProcess.getName()));

                while (scanner.hasNext()) {
                    Scanner scanner2 = new Scanner(scanner.next()); 
                    scanner2.useDelimiter("\\|nTa\\|");

                    ArrayList<String> row = new ArrayList<String>();

                    while(scanner2.hasNext()) {
                        row.add(scanner2.next());   
                    }

                    for (int index = 0; index < row.size(); index++) {
                        if (index == commentExtractField ||
                                index == contentExtractField ||
                                index == descriptionField) {
                            output.write(jsoupParse(row.get(index)).getBytes("UTF-8"));
                        }

                        else {
                            output.write(row.get(index).getBytes("UTF-8"));
                        }   

                        String delimiter = "";

                        if (index == row.size() - 1) {
                            delimiter = "|nLa|";
                        }

                        else {
                            delimiter = "|nTa|";
                        }

                        output.write(delimiter.getBytes("UTF-8"));
                    }
                }

                output.finish();
                output.close();
                scanner.close();
                gzipInputStream.close();


            }
        }
    }

    public static Long getTimestamp(File f) {
        String name = f.getName();
        String removeExt = name.substring(0, name.length() - 3);
        String timestamp = removeExt.substring(7, removeExt.length());
        return Long.parseLong(timestamp);
    }

    public static String jsoupParse(String s) {
        if (s.length() == 4) {
            return s;
        }

        else {
            return Jsoup.parse(s).text();
        }
    }
}

Как я могу убедиться, что, когда я закончу с объектами, они будут уничтожены и не используют никаких ресурсов? Например, каждый раз, когда я закрываю GZIPInputStream, GZIPOutputStream и Scanner, как я могу убедиться, что они полностью уничтожены?

Для записи, ошибка, которую я получаю:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2882)
at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:100)
at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:572)
at java.lang.StringBuilder.append(StringBuilder.java:203)
at org.jsoup.parser.TokeniserState$47.read(TokeniserState.java:1171)
at org.jsoup.parser.Tokeniser.read(Tokeniser.java:42)
at org.jsoup.parser.TreeBuilder.runParser(TreeBuilder.java:101)
at org.jsoup.parser.TreeBuilder.parse(TreeBuilder.java:53)
at org.jsoup.parser.Parser.parse(Parser.java:24)
at org.jsoup.Jsoup.parse(Jsoup.java:44)
at ParseHTML.jsoupParse(ParseHTML.java:125)
at ParseHTML.main(ParseHTML.java:81)

Ответы [ 5 ]

3 голосов
/ 09 февраля 2012

Я не потратил много времени на анализ вашего кода (ничего не выделяется), но хорошим началом общего назначения было бы ознакомление с бесплатным VisualVM инструментом. Это - разумное руководство по его использованию, хотя есть еще много статей.

На мой взгляд, есть лучшие коммерческие профилировщики - JProfiler для одного - но он, по крайней мере, покажет вам, на какие объекты / классы назначается большая часть памяти, и, возможно, трассировки стека методов, которые вызвали это. Проще говоря, он показывает вам распределение кучи с течением времени, и вы можете использовать это, чтобы определить, не удалось ли вам что-то очистить или это неизбежный всплеск.

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

2 голосов
/ 09 февраля 2012

Обновление: эта проблема была исправлена ​​в JSoup 1.6.2

Мне кажется, что это, вероятно, ошибка в анализаторе JSoup, который вы используете ... в настоящее время документация для JSoup.parse () имеет предупреждение "БЕТА: если вы получаете Возникло исключение или неверное дерево разбора, пожалуйста, сообщите об ошибке. " Что говорит о том, что они не уверены, что это абсолютно безопасно для использования в рабочем коде.

Я также нашел несколько сообщений об ошибках, в которых упоминаются исключения из памяти, один из которых предполагает, что это происходит из-за статического разбора объектов ошибок, сохраняемых JSoup статически, и что понижение с JSoup 1.6.1 до 1.5.2 может быть обходным путем.

1 голос
/ 09 февраля 2012

Предполагая, что проблема не в коде JSoup, мы можем провести некоторую оптимизацию памяти. Например, ArrayList<String> row может быть удалено, так как оно содержит все проанализированные строки в памяти, но для анализа требуется только одна строка.

Внутренняя петля с удалением row:

//Caution! May contain obvious bugs!
while (scanner.hasNext()) {
    String scanStr = scanner.next();

    //manually count of rows to replace 'row.size()'
    int rowCount = 0;
    int offset = 0;
    while ((offset = scanStr.indexOf("|nTa|", offset)) >= 0) {
        rowCount++;
        offset++;
    }
    rowCount++;

    Scanner scanner2 = new Scanner(scanStr);
    scanner2.useDelimiter("\\|nTa\\|");

    int index = 0;
    while (scanner2.hasNext()) {
        String curRow = scanner2.next();

        if (index == commentExtractField
               || index == contentExtractField
               || index == descriptionField) {
            output.write(jsoupParse(curRow).getBytes("UTF-8"));
        } else {
            output.write(curRow.getBytes("UTF-8"));
        }

        String delimiter = "";
        if (index == rowCount - 1) {
            delimiter = "|nLa|";
        } else {
            delimiter = "|nTa|";
        }

        output.write(delimiter.getBytes("UTF-8"));
    }
}
1 голос
/ 09 февраля 2012

Трудно сказать, что происходит, но мне приходят на ум две вещи.

1) В некоторых странных обстоятельствах (в зависимости от входного файла) следующий цикл может загрузить весь файл в память:

while(scanner2.hasNext()) {
    row.add(scanner2.next());
}

2) Глядя на stackTrace, кажется, что проблема в jsoupParse.Я полагаю, что эта строка Jsoup.parse(s).text(); сначала загружает s в память, и в зависимости от размера строки (что опять-таки зависит от ввода конкретного файла), это может привести к OutOfMemoryError

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

Всегда ли это происходит с одним и тем же файлом?Вы проверяли входной контент и пользовательские разделители в нем?

1 голос
/ 09 февраля 2012

Мне интересно, если ваш анализ не удался, потому что у вас есть плохой HTML (например, незакрытые теги, непарные кавычки или еще много чего)?Вы можете сделать вывод / println, чтобы увидеть, как далеко вы продвигаетесь в документе, если вообще.Библиотека Java может не понимать конец документа / файла до исчерпания памяти.

парсинг публичный статический парсинг документа (String html) парсинг HTML в документ.Поскольку базовый URI не указан, абсолютное определение URL основывается на HTML, включая тег.

http://jsoup.org/apidocs/org/jsoup/Jsoup.html#parse(java.lang.String)

...