10

私のアプリケーションは集中的な文字列処理でマルチスレッド化されています。過度のメモリ消費が発生しており、プロファイリングにより、これは文字列データが原因であることが示されました。ある種の flyweight パターンの実装やキャッシュを使用することで、メモリ消費が大幅に改善されると思います (文字列が複製されることが多いことは確かですが、その点に関するハード データはありません)。

Java Constant Pool と String.intern を確認しましたが、PermGen の問題を引き起こす可能性があるようです。

javaでアプリケーション全体のマルチスレッド化された文字列プールを実装するための最良の代替手段は何ですか?

編集: 私の以前の関連する質問も参照してください: How does Java implement flyweight pattern for string under the hood?

4

5 に答える 5

8

注: この回答では、最新のランタイム JVM ライブラリに関連しない可能性のある例を使用しています。特に、このsubstring例は OpenJDK/Oracle 7 以降では問題になりません。

人々がよく言うことに反することはわかっていますが、新しいStringインスタンス明示的に作成すると、メモリを大幅に削減する場合があります。

文字列は不変であるため、いくつかのメソッドはその事実を利用し、バッキング文字配列を共有してメモリを節約します。ただし、場合によっては、これらの配列の未使用部分のガベージ コレクションを防止することで、実際にメモリを増やすことができます。

たとえば、ログ ファイルのメッセージ ID を解析して警告 ID を抽出しているとします。コードは次のようになります。

//Format:
//ID: [WARNING|ERROR|DEBUG] Message...
String testLine = "5AB729: WARNING Some really really really long message";

Matcher matcher = Pattern.compile("([A-Z0-9]*): WARNING.*").matcher(testLine);
if ( matcher.matches() ) {
    String id = matcher.group(1);
        //...do something with id...
}

しかし、実際に保存されているデータを見てください。

    //...
    String id = matcher.group(1);
    Field valueField = String.class.getDeclaredField("value");
    valueField.setAccessible(true);

    char[] data = ((char[])valueField.get(id));
    System.out.println("Actual data stored for string \"" + id + "\": " + Arrays.toString(data) );

マッチャーは同じ文字データを新しい String インスタンスでラップするだけなので、これはテスト行全体です。に置き換えたときの結果を比較しString id = matcher.group(1);ますString id = new String(matcher.group(1));

于 2010-05-26T19:05:14.503 に答える
3

これは、JVM レベルですでに行われています。new String明示的または暗黙的に毎回 sを作成していないことを確認する必要があるだけです。

つまり、しないでください:

String s1 = new String("foo");
String s2 = new String("foo");

これにより、ヒープに 2 つのインスタンスが作成されます。むしろそうしてください:

String s1 = "foo";
String s2 = "foo";

これにより、ヒープに 1 つのインスタンスが作成され、両方が同じものを参照します (証拠として、ここs1 == s2に返さtrueれます)。

+=また、(ループ内で) 文字列を連結するために使用しないでください。

String s = "";
for (/* some loop condition */) {
    s += "new";
}

+=暗黙的new Stringに毎回ヒープに を作成します。むしろそうしろ

StringBuilder sb = new StringBuilder();
for (/* some loop condition */) {
    sb.append("new");
}
String s = sb.toString();

可能であれば、「集中的な文字列処理」の代わりにStringBuilderまたはその同期兄弟を使用してください。、 、 など、まさにこれらの目的に役立つメソッドを提供します。その javadocも参照してください。StringBufferStringappend()insert()delete()

于 2010-05-26T18:10:48.550 に答える
1

文字列をメモリに効率的にパックします!私はかつて、文字列がツリーとして格納される、ハイパーメモリ効率の高いSetクラスを作成しました。文字をトラバースして葉に到達した場合、エントリはセットに含まれていました。作業も速く、大きな辞書を保存するのに理想的です。

また、文字列は、私がプロファイリングしたほぼすべてのアプリのメモリ内で最大の部分であることが多いことを忘れないでください。必要な場合は、文字列を気にしないでください。

図:

ビール、豆、血の3つの弦があります。次のようなツリー構造を作成できます。

B
+-e
  +-er
  +-ans
+-lood

たとえば、通りの名前のリストには非常に効率的です。挿入を効率的に行うことができないため、これは固定辞書を使用する場合に明らかに最も合理的です。実際、構造は一度作成してからシリアル化し、その後ロードする必要があります。

于 2010-05-26T20:23:36.390 に答える
0

最初に、その解析の一部を排除した場合に、アプリケーションと開発者がどれだけ苦しむかを判断します。その過程で従業員の離職率が2倍になると、より速いアプリケーションは役に立ちません! あなたの質問に基づいて、あなたはすでにこのテストに合格したと推測できます。

第 2 に、オブジェクトの作成を排除できない場合、次の目標は、それが Eden コレクションに耐えられないようにすることです。そして parse-lookup はその問題を解決できます。ただし、「適切に実装された」キャッシュ (その基本的な前提には同意しませんが、付随する暴言で退屈させません) は通常、スレッドの競合を引き起こします。ある種類のメモリ プレッシャーを別のものに置き換えることになります。

パーズルックアップのイディオムには、完全なキャッシングから通常得られるような巻き添え被害の少ないバリエーションがあり、それは事前に計算された単純なルックアップ テーブルです (「メモ化」も参照)。これに対して通常目にするパターンは、タイプ セーフ列挙(TSE) です。TSE では、String を解析し、それを TSE に渡して関連する列挙型を取得してから、String を破棄します。

処理しているテキストは自由形式ですか、それとも入力は厳密な仕様に従う必要がありますか? 多くのテキストが可能な値の固定セットにレンダリングされる場合、TSE はここで役立ち、より優れたマスターを提供します: 使用時点ではなく、作成時点で情報にコンテキスト/セマンティクスを追加します。 .

于 2010-05-26T18:38:23.003 に答える