7

これは私たちが理解するのに苦労した質問です。テキストで説明するのは難しいですが、要点を理解していただければ幸いです。

文字列の実際の内容が内部char配列で囲まれていることを理解しています。通常、文字列の保持されるヒープサイズには、40バイトと文字配列のサイズが含まれます。これはここで説明されています。サブ文字列を呼び出す場合、文字配列は元の文字列への参照を保持するため、文字配列の保持サイズは文字列自体よりもはるかに大きくなる可能性があります。

ただし、YourkitまたはMATを使用してメモリ使用量をプロファイリングすると、何か奇妙なことが起こるようです。char配列の保持サイズを参照する文字列には、文字配列の保持サイズは含まれません。

例は次のとおりです(半擬似コード):

String date = "2011-11-33"; (24 bytes)
date.value = char{1172}; (2360 bytes)

文字列の保持サイズは、文字配列の保持サイズを含まない24バイトとして定義されます。これは、多くの部分文字列操作のために文字配列への参照が多い場合に意味があります。

この文字列が配列やリストなどのコレクションのタイプに含まれている場合、この配列の保持サイズには、文字配列の保持サイズを含むすべての文字列の保持サイズが含まれます。

次に、次のような状況になります。

Array's retained size = 300 bytes
array[0] = String 40 bytes;
array[1] = String 40 bytes;
array[1].value = char[] (220 bytes)

したがって、保持されたサイズがどこから来ているのかを理解するために、各配列エントリを調べる必要があります。

繰り返しますが、これは、配列が同じ文字配列への参照を保持するすべての文字列を保持しているため、配列の保持サイズが完全に正しいことで説明できます。

ここで問題が発生します。

上記で説明した配列への参照と、同じ文字列を持つ別の配列を別のオブジェクトに保持します。両方の配列で、文字列は同じ文字配列を参照しています。これは予想されることです-結局のところ、同じ文字列について話しているのです。ただし、この文字配列の保持サイズは、この新しいオブジェクトの両方の配列でカウントされます。言い換えれば、保持されるサイズは2倍のようです。最初の配列を削除しても、2番目の配列は文字配列への参照を保持します。その逆も同様です。これは、Javaが同じ文字配列への2つの別々の参照を保持しているように見えるという点で混乱を引き起こします。どうすればいいの?これはJavaのメモリの問題ですか、それともプロファイラーが情報を表示する方法ですか?

この問題は、アプリケーションの膨大なメモリ使用量を追跡しようとするときに多くの頭痛の種を引き起こしました。

繰り返しになりますが、誰かが質問を理解して説明できるようになることを願っています。

ご協力いただきありがとうございます

4

4 に答える 4

4

上記で説明した配列への参照と、同じ文字列を持つ別の配列を別のオブジェクトに保持します。両方の配列で、文字列は同じ文字配列を参照しています。これは予想されることです-結局のところ、同じ文字列について話しているのです。ただし、この文字配列の保持サイズは、この新しいオブジェクトの両方の配列でカウントされます。言い換えれば、保持されるサイズは2倍のようです。

ここにあるのは、ドミネーターツリーの推移的な参照です

ここに画像の説明を入力してください

文字配列は、どちらの配列の保持サイズにも表示されないようにする必要があります。プロファイラーがそのように表示する場合、それは誤解を招く恐れがあります。

これは、JProfilerが最大オブジェクトビューでこの状況を示す方法です。

ここに画像の説明を入力してください

両方の配列に含まれる文字列インスタンスは、[transitivereference]ラベルとともに配列インスタンスの外側に表示されます。実際のパスを調べたい場合は、配列ホルダーと文字列をグラフに追加して、それらの間のすべてのパスを見つけることができます。

ここに画像の説明を入力してください

免責事項:私の会社はJProfilerを開発しています。

于 2011-12-08T10:52:46.037 に答える
3

これは、プロファイラーが情報を表示する方法にすぎないと思います。2つのアレイを「重複排除」と見なす必要があるかどうかはわかりません。2つの配列をある種のダミーホルダーオブジェクトにラップし、それに対してプロファイラーを実行するのはどうですか?そうすれば、「ダブルカウント」に対応できるはずです。

于 2011-12-08T08:11:31.690 に答える
0

文字列がインターンされていない限り、インターンすることはできますが、インターンすることはできequal()ません==。char配列からStringオブジェクトを作成する場合、コンストラクターはchar配列のコピーを作成します。(これは、char配列値のその後の変更から不変の文字列を保護する唯一の方法です。)

于 2011-12-08T08:10:36.370 に答える
0

で実行する場合-XX:-UseTLAB

public static void main(String... args) throws Exception {
    StringBuilder text = new StringBuilder();
    text.append(new char[1024]);
    long free1 = free();
    String str = text.toString();
    long free2 = free();
    String [] array = { str.substring(0, 100), str.substring(101, 200) };
    long free3 = free();
    if (free3 == free2)
        System.err.println("You must use -XX:-UseTLAB");
    System.out.println("To create String with 1024 chars "+(free1-free2)+" bytes\nand to create an array with two sub-string was "+(free2-free3));
}

private static long free() {
    return Runtime.getRuntime().freeMemory();
}

プリント

To create String with 1024 chars 2096 bytes
and to create an array with two sub-string was 88

同じバックエンドストアを共有している場合に予想されるより多くのメモリを消費していることがわかります。

Stringクラスのコードを見ると。

public String substring(int start, int end) {
    // checks.
    return ((beginIndex == 0) && (endIndex == count)) ? this :
        new String(offset + beginIndex, endIndex - beginIndex, value);
}

String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

Stringのサブ文字列は、基になる値の配列のコピーを取得しないことがわかります。


考慮すべきもう1つのこと-XX:+UseCompressedStringsは、JVMの新しいバージョンではデフォルトでオンになっていることです。これにより、JVMは可能な場合はchar[]ではなくbyte[]を使用するようになります。

文字列および配列オブジェクトのヘッダーのサイズは、32ビットJVM、32ビット参照の64ビットJVM、および64ビット参照の64ビットJVMによって異なります。

于 2011-12-08T08:14:39.630 に答える