7

ご存知のとおり、Javaはラッパーや文字列型にオブジェクトプールを使用する場合もあれば、使用しない場合もあります。

例えば:

Integer i1 = 1;
Integer i2 = 1;
Integer i3 = new Integer(1);
String s1 = "String";
String s2 = "String";
String s3 = new String ("String");
System.out.println("(i1 == i2) " + (i1 == i2));
System.out.println("(i2 == i3) " + (i2 == i3));
System.out.println("(s1 == s2) " + (s1 == s2));
System.out.println("(s2 == s3) " + (s2 == s3));

Execution result:

(i1 == i2) true
(i2 == i3) false
(s1 == s2) true
(s2 == s3) false

ご覧のとおり、プリミティブのボクシングはプールからオブジェクトを取得しますが、文字列リテラルを介して文字列を作成すると、プールからもオブジェクトが取得されます。このようなオブジェクトは実際には同じオブジェクトです(演算子==はそれらに対してtrueを返します)。

ラッパーと文字列を作成する他のメカニズムは、プールからオブジェクトを取得しません。これらの方法で作成されたオブジェクトは、実際には異なるオブジェクトです(operator ==はそれらに対してfalseを返します)。

私を混乱させるのは、プールが部分的に使用されているという事実です。

メモリの問題である場合は、プールを常に使用してみませんか?それがメモリの問題ではない場合-なぜそれを使用するのですか?

問題は、そのような動作(=プールの部分的な使用)を実装する理由は何ですか?

質問はかなり理論的ですが、実用的なアプリケーションがあります。カスタムオブジェクトプールを正しく使用する方法を理解するのに役立ちます。もちろん、Javaがどのように機能するかを理解することは常に良いことです。

4

5 に答える 5

5

メモリの問題である場合は、プールを常に使用してみませんか?

すべてを常にプールすることは、失敗することがある単純なキャッシュよりもコストがかかります。より高度なデータ構造(小さな整数のプールは単純で小さな配列で実行できます)とアルゴリズムが必要であり、プールはあなたを助けません。さらに、二度と必要とされない多くのオブジェクトをプールすることになります。これはメモリの浪費です。より多くの(役に立たない)キャッシュエントリが必要であり、そのキャッシュを管理する必要があります(または役に立たないオブジェクトを存続させるのに苦労します)。

それがメモリの問題ではない場合-なぜそれを使用するのですか?

これメモリの問題です。この最適化により、多くのメモリが節約されます。すべてのオブジェクトが頻繁に使用されるわけではないため、すべてのオブジェクトをプールしても、必ずしもメモリ使用量が削減されるわけではありません。それはトレードオフです。採用されたアプローチは、他の操作を過度に遅くしたり、大量のメモリを浪費したりすることなく、いくつかの一般的なユースケースでかなりの量のメモリを節約します。

于 2013-01-18T16:14:40.517 に答える
3

さまざまな考慮事項があります。たとえば、メモリ使用量だけでなく、言語セマンティクス。

 new Integer(1);

明示的なオブジェクトの作成です。これを「getfrompool」演算子に置き換えると、言語のセマンティクスが変更されます。

 Integer.valueOf(1)

明示的な「プール範囲内の場合は、プールから取得」です。プールは静的であり、仮想マシンではなくJavaで実装されていることに注意してください。あなたはそれを調べることができます:java.lang.Integer$IntegerCache。Javaの仕様では、inttoからのキャストは呼び出しIntegerを挿入することによって行われるとされていると思いInteger.valueOfます。

ここで、このキャッシュがどのように実装されているかを調べると、キャッシュサイズにユーザーが調整可能なパラメーターがあることがわかります。デフォルトでは、このキャッシュは、Integer[256]事前に初期化されたコピー-128..127(つまり、バイトの値の範囲)を保持する配列で構成されています。明らかに、この値の範囲は、一般的な使用に対して最高のパフォーマンスとメモリのトレードオフを提供します。

詳細については、たとえばhttp://martykopka.blogspot.de/2010/07/all-about-java-integer-cache.htmlを参照してください。

Javaで数値計算に時間を費やすと、オートボクシングの悪影響を実感できます。高性能の数値の場合、最初の経験則は、あらゆる種類のオートボクシングを回避することです。たとえば、GNU Troveは、プリミティブ型のハッシュマップと同様の構造を提供し、ランタイムとメモリの利点は計り知れません。

オブジェクトは16バイトのIntegerメモリを占有します-の4倍intです。したがって、上記のキャッシュは、たとえば約5kbのメモリを占有します。これは、ほとんどのアプリケーションが無駄にすることができるものです。しかし、明らかに、すべての整数に対してこれを行うことはできません!

文字列に関しては、コンパイラはそれらをクラスファイルに適切に格納する必要があります。では、どのようにして文字列をクラスファイルに格納しますか?最も簡単な方法は、コードを次のように書き直すことです。

private static final char[] chars_12345 = new char[]{ 't', 'e', 's', 't'};

private static final String CONST_STRING_12345 = new String(chars_12345);

これは、このタイプの魔法の処理に依存しませんString。これは、プリミティブのラップされた配列にすぎません。そしてもちろん、クラスのサイズを減らしてロード時間を短縮するために、クラスごとに1回だけ一意の文字列を格納する必要があります。

于 2013-01-18T16:10:01.013 に答える
2

速度の問題であり、Integer毎回新しいものを割り当てると、時間とメモリの両方が高価になります。しかし、同じトークンを起動時に割り当てると、起動時に大量のメモリと時間が使用されます。

悲しいことに、あなたが見つけたように、それはいくつかの直感に反する振る舞いにつながります。

結果は、私たちが持っているこの奇妙な妥協です。この動作の理由は、Java標準で説明されています。(5.7)

ボックス化されている値pがtrue、false、バイト、\u0000から\u007fの範囲の文字、または-128から127までの整数または短い数値の場合、r1とr2を任意の2つのボックス変換の結果とします。 pの。r1==r2の場合は常にそうです。理想的には、与えられたプリミティブ値pをボックス化すると、常に同一の参照が生成されます。実際には、これは既存の実装手法を使用して実行できない場合があります。上記のルールは実用的な妥協案です。上記の最後の節では、特定の共通の値を常に区別できないオブジェクトにボックス化する必要があります。実装は、これらを遅延または熱心にキャッシュする場合があります。

他の値の場合、この定式化では、プログラマー側のボックス化された値のIDに関する仮定は許可されません。これにより、これらの参照の一部またはすべてを共有できるようになります(必須ではありません)。

これにより、ほとんどの場合、特に小さなデバイスで過度のパフォーマンスペナルティを課すことなく、動作が望ましいものになります。メモリ制限の少ない実装では、たとえば、すべての文字とショート、および-32Kから+32Kの範囲の整数とロングをキャッシュできます。

tl; dr

それを完全に機能させることは不可能であり、それをまったく機能させないのはあまりにも奇妙です。したがって、「ほとんどの時間」機能します。

于 2013-01-18T16:10:17.187 に答える
1

それはメモリと速度の場合です。ほとんどの場合、JVMが使用されることはないため、JVMの起動時に40億個の整数オブジェクトを作成する必要はありません。

文字列の場合、インターンプールで一致する文字列を見つけるという問題もあります。

ソースコード内の文字列リテラルは、コンパイラがそれらを見つけてインターンするように調整できるため簡単ですが、実行時に文字列を動的に作成する場合、JVMはプールを調べてすべての文字列を検索し、一致して再利用できるかどうかを確認する必要があります。文字列はできます

それをスピードアップするのに役立つデータ構造がありますが、一般的には、新しいオブジェクトを作成する方が簡単で高速です。

于 2013-01-18T16:11:00.230 に答える
1

次のコードセグメントを参照してください。

Integer i1=1;
Integer i2=2;
String s1="String";
String s2="String";

、はオブジェクトi1i2はなく参照のみであり、ブースにはオブジェクトが割り当てられます1。同じようs1に、s2オブジェクトではなく参照のみであり、ブースにはオブジェクトが割り当てられます"String"

一方、次のコードでは:

Integer i3=new Integer(1);
String s3=new String("String");

new演算子はを作成しますnew Object。私があなたを片付けたことを願っています。

于 2013-01-18T16:11:04.593 に答える