0

アプリケーションの一部で、17MBのログファイルをリスト構造(1行に1つのLogEntry)に解析しています。約100K行/ログエントリがあります。1行あたり170バイト。私が驚いたのは、128MBを指定してもヒープスペースが不足していることです(256MBで十分なようです)。10MBのテキストをオブジェクトのリストに変換すると、どのようにしてスペースが10倍になりますか?

StringオブジェクトはANSIテキスト(Unicode、1文字= 2バイト)と比較して少なくとも2倍のスペースを使用することを理解していますが、これは少なくとも4倍のスペースを消費します。

私が探しているのは、n個のLogEntriesのArrayListが消費する量の概算、または私のメソッドが状況を悪化させる無関係なオブジェクトを作成する方法です (以下のコメントを参照String.trim()

これは私のLogEntryクラスのデータ部分です

public class LogEntry { 
    private Long   id; 
    private String system, version, environment, hostName, userId, clientIP, wsdlName, methodName;
    private Date                timestamp;
    private Long                milliSeconds;
    private Map<String, String> otherProperties;

これは読書をしている部分です

public List<LogEntry> readLogEntriesFromFile(File f) throws LogImporterException {
    CSVReader reader;
    final String ISO_8601_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss,SSS";

    List<LogEntry> logEntries = new ArrayList<LogEntry>();
    String[] tmp;
    try {
        int lineNumber = 0;
        final char DELIM = ';';
        reader = new CSVReader(new InputStreamReader(new FileInputStream(f)), DELIM);
        while ((tmp = reader.readNext()) != null) {
            lineNumber++;

            if (tmp.length < LogEntry.getRequiredNumberOfAttributes()) {

                String tmpString = concat(tmp);

                if (tmpString.trim().isEmpty()) {
                    logger.debug("Empty string");
                } else {
                    logger.error(String.format(
                            "Invalid log format in %s:L%s. Not enough attributes (%d/%d). Was %s . Continuing ...",
                            f.getAbsolutePath(), lineNumber, tmp.length, LogEntry.getRequiredNumberOfAttributes(), tmpString)
                    );
                }

                continue;
            }

            List<String> values = new ArrayList<String>(Arrays.asList(tmp));
            String system, version, environment, hostName, userId, wsdlName, methodName;
            Date timestamp;
            Long milliSeconds;
            Map<String, String> otherProperties;

            system = values.remove(0);
            version = values.remove(0);
            environment = values.remove(0);
            hostName = values.remove(0);
            userId = values.remove(0);
            String clientIP = values.remove(0);
            wsdlName = cleanLogString(values.remove(0));
            methodName = cleanLogString(stripNormalPrefixes(values.remove(0)));
            timestamp = new SimpleDateFormat(ISO_8601_DATE_PATTERN).parse(values.remove(0));
            milliSeconds = Long.parseLong(values.remove(0));

            /* remaining properties are the key-value pairs */
            otherProperties = parseOtherProperties(values);

            logEntries.add(new LogEntry(system, version, environment, hostName, userId, clientIP,
                    wsdlName, methodName, timestamp, milliSeconds, otherProperties));
        }
        reader.close();
    } catch (IOException e) {
        throw new LogImporterException("Error reading log file: " + e.getMessage());
    } catch (ParseException e) {
        throw new LogImporterException("Error parsing logfile: " + e.getMessage(), e);
    }

    return logEntries;
}

マップの作成に使用されるユーティリティ関数

private Map<String, String> parseOtherProperties(List<String> values) throws ParseException {
    HashMap<String, String> map = new HashMap<String, String>();

    String[] tmp;
    for (String s : values) {
        if (s.trim().isEmpty()) {
            continue;
        }

        tmp = s.split(":");
        if (tmp.length != 2) {
            throw new ParseException("Could not split string into key:value :\"" + s + "\"", s.length());
        }
        map.put(tmp[0], tmp[1]);
    }
    return map;
}
4

2 に答える 2

2

他のプロパティを保存するマップもあります。コードには、このマップがどのように設定されるかは示されていませんが、マップには、エントリ自体に必要なメモリと比較して、大量のメモリオーバーヘッドがある可能性があることに注意してください。

マップをサポートする配列のサイズ(少なくとも16エントリ* 4バイト)+エントリごとに1つのキー/値のペア+データ自体のサイズ。それぞれキーに10文字、値に10文字を使用する2つのマップエントリは、16 * 4 + 2 * 2 * 4 + 2 * 10 * 2 + 2 * 10 * 2 + 2 * 2 * 8 = 64 +16+を消費します。 40 + 40 + 24 = 184バイト(1文字= 2バイト、Stringオブジェクトは最小8バイトを消費します)。それだけで、ログ文字列全体のスペース要件がほぼ2倍になります。

これに加えて、LogEntryには12個のオブジェクト、つまり少なくとも96バイトが含まれています。したがって、ログオブジェクトだけでも、マップや実際の文字列データがなくても、約100バイトが必要になります。さらに、参照用のすべてのポインター(それぞれ4B)。私はマップで少なくとも18を数えます。これは、72バイトを意味します。

データの追加(-最後の段落で説明したオブジェクト参照とオブジェクト「ヘッダー」):
2つのlong = 16B、1つの日付がlong = 8Bとして保存され、マップ=184B。さらに、文字列の内容、たとえば90文字=180バイトが含まれます。リストに入れると、おそらくリスト項目の両端に1〜2バイトあるので、合計で約100 + 72 + 16 + 8 + 184 + 180 = 560〜600バイト/ログ行になります。

したがって、ログ行あたり約600バイト、つまり100K行は最小で約60MBを消費します。これにより、少なくとも、サイズに設定されたヒープサイズと同じ桁数で配置されます。さらに、ループ内のtmpString.trim()が文字列のコピーを作成している可能性があるという事実があります。同様に、String.format()もコピーを作成している可能性があります。アプリケーションの残りの部分もこのヒープスペース内に収まる必要があり、残りのメモリがどこに行くのかを説明する場合があります。

于 2013-02-07T11:47:49.693 に答える
0

String各オブジェクトは、実際の定義にスペース(24バイト?)Objectに加えて、char配列への参照、オフセット(使用用)などを消費することを忘れないでくださいsubstring()。したがって、行を「n」文字列として表すと、追加のストレージ要件が追加されます。 。LogEntry代わりに、クラス内でこれらを怠惰に評価できますか?

(文字列オフセットの使用法-Java 7b6より前はString.substring()、既存のchar配列へのウィンドウとして機能するため、オフセットが必要です。これは最近変更されたため、後のJDKビルドの方がメモリ効率が高いかどうかを判断する価値があります)

于 2013-02-07T11:40:25.223 に答える