5

文字列を解析する単純なスキャナーを実行していますが、頻繁に呼び出すと OutOfMemory エラーが発生することがわかりました。このコードは、文字列の配列に対して繰り返し構築されるオブジェクトのコンストラクターの一部として呼び出されます。

編集:詳細情報のコンストラクターは次のとおりです。Scanner に関する try-catch 以外では、それほど多くは発生していません

   public Header(String headerText) {
        char[] charArr;
        charArr = headerText.toCharArray();
        // Check that all characters are printable characters
        if (charArr.length > 0 && !commonMethods.isPrint(charArr)) {
            throw new IllegalArgumentException(headerText);
        }
        // Check for header suffix
        Scanner sc = new Scanner(headerText);
        MatchResult res;
        try {
            sc.findInLine("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");
            res = sc.match();
        } finally {
            sc.close();
        }

        if (res.group(1) == null || res.group(1).isEmpty()) {
            throw new IllegalArgumentException("Missing header keyword found");     // Empty header to store
        } else {
            mnemonic = res.group(1).toLowerCase();                            // Store header
        }
        if (res.group(2) == null || res.group(2).isEmpty()) {
            suffix = -1;
        } else {
            try {
                suffix = Integer.parseInt(res.group(2));       // Store suffix if it exists
            }  catch (NumberFormatException e) {
                throw new NumberFormatException(headerText);
            }
        }
        if (res.group(3) == null || res.group(3).isEmpty()) {
            isQuery= false;
        } else {
            if (res.group(3).equals("?")) {
                isQuery = true;
            } else {
                throw new IllegalArgumentException(headerText);
            }
        }

        // If command was of the form *ABC, reject suffixes and prefixes
        if (mnemonic.contains("*") 
                && suffix != -1) {
            throw new IllegalArgumentException(headerText);
        }
    }

プロファイラーのメモリ スナップショットは、Scanner.findInLine() の read(Char) メソッドが、数十万の文字列をスキャンする操作中に大量のメモリを割り当てられることを示しています。数秒後、すでに 38MB 以上が割り当てられています。

ここに画像の説明を入力

コンストラクターで使用した後にスキャナーで close() を呼び出すと、GC によってクリアされる古いオブジェクトにフラグが立てられると思いますが、どういうわけかそれは残り、読み取りメソッドはヒープを満たす前にギガバイトのデータを蓄積します。

誰かが私を正しい方向に向けることができますか?

4

6 に答える 6

2

Patternすべてのコードを投稿したわけではありませんが、同じ正規表現を繰り返しスキャンしていることを考えると、事前に静的をコンパイルし、これをスキャナーの検索に使用する方がはるかに効率的です。

static Pattern p = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)");

そしてコンストラクターで:

sc.findInLine(p);

これが OOM の問題の原因である場合とそうでない場合がありますが、解析が少し速くなることは間違いありません。

関連: java.util.regex - Pattern.compile() の重要性?

更新:コードをさらに投稿した後、他の問題がいくつか見られます。このコンストラクターを繰り返し呼び出す場合は、おそらく事前に入力をトークン化または分割していることを意味します。Scanner各行を解析するために新しいものを作成するのはなぜですか? それらは高価です; Scanner可能であれば、ファイル全体を解析するために同じものを使用する必要があります。Scannerプリコンパイル済みのものを使用Patternすると、現在行っていることよりもはるかに高速になります。これは、解析している行ごとに新しいScannerおよび新しいを作成することです。Pattern

于 2013-03-21T17:54:13.480 に答える
1

あなたの記憶をいっぱいにしている文字列はで作成されましたfindInLine()。したがって、繰り返しPattern作成することは問題ではありません。

コードの残りの部分が何をするのかわからない場合、マッチャーから取得したグループの1つがオブジェクトのフィールドに保持されていると思います。次に、ここに示すように、その文字列はに割り当てられますfindInLine()が、保持されているという事実は、コードが原因です。

編集:

これがあなたの問題です:

mnemonic = res.group(1).toLowerCase();

気付かないかもしれませんが、文字列に大文字が含まれていない場合にtoLowerCase()返されます。thisまた、をgroup(int)返します。これは、完全な文字列substring()と同じ文字列を基にした新しい文字列を作成しchar[]ます。したがって、mnemonic実際にchar[]は行全体のが含まれています。

修正は次のようになります。

mnemonic = new String(res.group(1).toLowerCase());
于 2013-03-21T18:02:23.573 に答える
0

コード スニペットがいっぱいではないと思います。scanner.findInLine()ループで呼び出していると思います。とにかく電話してみてくださいscanner.reset()。これで問題が解決することを願っています。

于 2013-03-21T17:44:59.983 に答える
0

JVM には明らかにガベージ コレクションの時間がありません。おそらく、同じコード (コンストラクター) を繰り返し使用して、同じクラスの複数のインスタンスを作成しているためです。JVM は、ランタイム スタックで何かが変更されるまで GC について何もしない場合がありますが、この場合はそうではありません。他のメソッドが呼び出されているとき、メモリ管理の動作の一部がまったく同じではないため、コンストラクターで「やりすぎる」ことについて過去に警告されました。

于 2013-03-21T17:45:23.757 に答える
0

問題の原因を突き止めました。正確には Scanner ではなく、コンストラクターでスキャンを実行しているオブジェクトを保持するリストです。

問題は、解析を含むオブジェクトへの参照を保持していたリストのオーバーランに関係していました。基本的に、単位時間あたりに処理できるよりも多くの文字列が受信され、RAM がなくなるまでリストが成長し続けました。このリストを最大サイズに制限することで、パーサーがメモリをオーバーロードするのを防ぐことができます。今後、このオーバーランを回避するために、パーサーとデータ ソース間の同期を追加する予定です。

提案していただきありがとうございます。スキャナーに関してパフォーマンスに関していくつかの変更を既に行っています。参照を保持している正確な犯人を追跡できるjvisualvmを教えてくれた@RobIに感謝します。メモリ ダンプに参照リンクが表示されませんでした。

于 2013-03-22T17:40:49.680 に答える
0

問題は、数十万の文字列をスキャンしていて、パターンを文字列として渡しているため、ループの反復ごとに新しいパターン オブジェクトがあることです。次のように、パターンをループから引き出すことができます。

    Pattern toMatch = Pattern.compile("(\\D*[a-zA-Z]+)(\\d*)(\\D*)")

    Scanner sc = new Scanner(headerText);
    MatchResult res;

    try {
        sc.findInLine(toMatch);
        res = sc.match();
    } finally {
        sc.close();
    }

toMatchそうすれば、一致を試みるたびに新しいパターン オブジェクトを作成するオーバーヘッドが発生する代わりに、オブジェクト参照を渡すだけになります。これで漏れが修正されます。

于 2013-03-21T17:55:19.937 に答える