多くのデータを含む変数を使用しました。たとえば、String data
。この文字列の一部を次の方法で使用したかったのです。
this.smallpart = data.substring(12,18);
数時間のデバッグ(メモリビジュアライザー)の後、オブジェクトフィールドsmallpart
はdata
からのすべてのデータを記憶していることがわかりましたが、サブストリングしか含まれていませんでした。
コードを次のように変更したとき:
this.smallpart = data.substring(12,18)+"";
..問題は解決しました!今、私のアプリケーションは非常に少ないメモリを使用しています!
そんなことがあるものか?誰でもこれを説明できますか? this.smallpartはデータを参照し続けていると思いますが、なぜですか?
PDATE:どうすれば大きな文字列をクリアできますか? data = new String(data.substring(0,100))はそれを行いますか?
以下を実行します。
_data.substring(x, y) + ""
_
新しい(より小さい)文字列オブジェクトを作成し、substring()によって作成された文字列への参照を破棄するため、このガベージコレクションが可能になります。
実現する重要なことは、substring()
がexistingString-またはむしろ元のStringの基礎となる文字配列にウィンドウを与えることです。 。したがって、元の文字列と同じメモリを消費します。これは状況によっては有利な場合がありますが、部分文字列を取得して元の文字列を破棄する場合は問題があります(わかっているように)。
詳細については、JDK Stringソースの substring()method をご覧ください。
編集:補足的な質問に答えるために、部分文字列から新しい文字列を構築すると、メモリ消費量が削減されますprovided元の文字列への参照をバインドします。
注(2013年1月)。上記の動作が変更されました in Java 7u6 。flyweightパターンは使用されなくなり、substring()
は期待どおりに動作します。
substring(int, int)
のソースを見ると、それが返されることがわかります。
_new String(offset + beginIndex, endIndex - beginIndex, value);
_
ここで、value
は元の_char[]
_です。したがって、新しい文字列を取得しますが、同じ基になる_char[]
_を使用します。
data.substring() + ""
を実行すると、new基になる_char[]
_を持つ新しい文字列を取得します。
実際、ユースケースは、String(String)
コンストラクターを使用する必要がある唯一の状況です。
_String tiny = new String(huge.substring(12,18));
_
substring
を使用する場合、実際には新しい文字列は作成されません。オフセットとサイズの制約を使用して、まだ元の文字列を参照しています。
したがって、元の文字列を収集するには、新しい文字列を作成する必要があります(new String
、またはあなたが持っているもの)。
This.smallpartはデータを参照し続けていると思いますが、なぜですか?
Java文字列は、char配列、開始オフセット、および長さ(およびキャッシュされたhashCode)で構成されます。substring()
などの文字列操作は、単純に異なるオフセットフィールドや長さフィールドを持つchar配列。これは、一度作成されたStringのchar配列は変更されないため機能します。
これにより、多くの部分文字列が重複部分を複製せずに同じ基本文字列を参照する場合にメモリを節約できます。お気づきのとおり、状況によっては、不要になったデータがガベージコレクションされないようにすることができます。
これを修正する「正しい」方法は、new String(String)
コンストラクターです。
this.smallpart = new String(data.substring(12,18));
ところで、全体的な最善の解決策は、そもそも非常に大きな文字列を持たず、一度に数KBの小さなチャンクで入力を処理することを避けることです。
In Java文字列はimutableオブジェクトであり、文字列が作成されると、ガベージコレクターによってクリーニングされるまでメモリ上に残ります(このクリーニングは当然のことではありません)。
Substringメソッドを呼び出すと、Javaは非常に新しい文字列を作成せず、元の文字列内に文字の範囲を格納するだけです。
そのため、このコードを使用して新しい文字列を作成したとき:
this.smallpart = data.substring(12, 18) + "";
結果を空の文字列と連結したときに、実際に新しい文字列を作成しました。それが理由です。
1997年のjwz で文書化されているように:
巨大な文字列がある場合は、そのsubstring()を引き出し、その部分文字列を保持し、長い文字列がゴミになる(つまり、部分文字列の有効期間が長くなる)ことができます。離れて。
まず、_Java.lang.String.substring
_を呼び出すと、オフセットと長さを使用して、元のString
に新しいウィンドウが作成されます基になる配列の重要な部分をコピーする代わりに =
substring
メソッドを詳しく見ると、string constructorがString(int, int, char[])
を呼び出し、_char[]
_全体を渡すことに気付くでしょう。それはstringを表します。つまり、substringは、元のstringと同量のメモリを占有します。
OK、しかし、なぜ_+ ""
_は、メモリがない場合よりも少ないメモリを要求するのですか?
strings
で_+
_を実行することは、_StringBuilder.append
_メソッド呼び出しを介して実装されます。 AbstractStringBuilder
クラスのこのメソッドの実装を見ると、本当に必要な部分(arraycopy
)で最終的にsubstring
を実行することがわかります。
その他の回避策??
_this.smallpart = new String(data.substring(12,18));
this.smallpart = data.substring(12,18).intern();
_
要約すると、少数の大きな文字列から多くの部分文字列を作成する場合は、
String subtring = string.substring(5,23)
大きな文字列を保存するためだけにスペースを使用しますが、大きな文字列の喪失からほんの一握りの小さな文字列を抽出する場合、
String substring = new String(string.substring(5,23));
大きな文字列は不要になったときに再利用できるため、メモリの使用量を抑えることができます。
new String
は、元の文字列への参照ではなく、実際に新しい文字列を取得していることを示す便利なリマインダーです。
文字列に「」を追加すると、場合によってメモリが節約されます。
100万文字の本全体を含む巨大な文字列があるとします。
次に、本の章を部分文字列として含む20個の文字列を作成します。
次に、すべての段落を含む1000個の文字列を作成します。
次に、すべての文を含む10,000個の文字列を作成します。
次に、すべての単語を含む100,000個の文字列を作成します。
まだ1,000,000文字しか使用していません。各章、段落、文、および単語に「」を追加する場合、5,000,000文字を使用します。
もちろん、本全体から1つのWordのみを抽出する場合はまったく異なります。また、本全体をガベージコレクションすることもできますが、その1つのWordがその参照を保持しているからではありません。
また、100万文字の文字列があり、両端のタブとスペースを削除し、サブストリングを作成するために10回の呼び出しを行う場合も同様です。 Javaが動作または動作する方法は、毎回100万文字をコピーすることを回避します。妥協点があります。妥協点を知っていると便利です。