2013-04-22 24 views
6

文字列が作成されたときにメモリ割り当てに関する多くの矛盾した記事を読んだ。 new演算子がヒープにStringを作成し、StringリテラルがString Pool [Heap]で作成されるとの記述がありますが、new演算子はヒープ内にオブジェクトを作成し、Stringプール内に別のオブジェクトを作成します。これを分析するために文字列作成と文字配列メモリ割り当て

iは文字列の文字列と文字列オブジェクトのハッシュコードを印刷する次のプログラムを書いた:

import java.lang.reflect.Field; 

public class StringAnalysis { 

    private int showInternalCharArrayHashCode(String s) 
      throws SecurityException, NoSuchFieldException, 
      IllegalArgumentException, IllegalAccessException { 
     final Field value = String.class.getDeclaredField("value"); 
     value.setAccessible(true); 
     return value.get(s).hashCode(); 
    } 

    public void printStringAnalysis(String s) throws SecurityException, 
      IllegalArgumentException, NoSuchFieldException, 
      IllegalAccessException { 
     System.out.println(showInternalCharArrayHashCode(s)); 

     System.out.println(System.identityHashCode(s)); 

    } 

    public static void main(String args[]) throws SecurityException, 
      IllegalArgumentException, NoSuchFieldException, 
      IllegalAccessException, InterruptedException { 
     StringAnalysis sa = new StringAnalysis(); 
     String s1 = new String("myTestString"); 
     String s2 = new String("myTestString"); 
     String s3 = s1.intern(); 
     String s4 = "myTestString"; 

     System.out.println("Analyse s1"); 
     sa.printStringAnalysis(s1); 

     System.out.println("Analyse s2"); 
     sa.printStringAnalysis(s2); 

     System.out.println("Analyse s3"); 
     sa.printStringAnalysis(s3); 

     System.out.println("Analyse s4"); 
     sa.printStringAnalysis(s4); 

    } 

} 

このプログラム印刷次の出力:

Analyse s1 
1569228633 
778966024 
Analyse s2 
1569228633 
1021653256 
Analyse s3 
1569228633 
1794515827 
Analyse s4 
1569228633 
1794515827 

この出力一方からストリングがどのように作成されるかにかかわらず、ストリングが同じ値を持つ場合、それらは同じchar配列を共有するということは非常に明確です。

私の質問は、このchararrayがどこに格納されているか、ヒープに格納されているのか、それともpermgenに行くのでしょうか?また、私はヒープメモリアドレスとpermgenメモリアドレスを区別する方法を理解したい。

私が貴重な限られたpermgenスペースを食べるようにpermgenに格納されていると、大きな問題があります。 char配列がpermgenに格納されているのではなくヒープに格納されている場合は、文字列リテラルもヒープスペース[これまで読んでいないもの]を使用することを意味します。

+0

Javaコンパイラは、あまりにも賢いです。 '' ... ... toCharArray() 'などを試してみてください。しかし、情報レベルはゼロになる。 –

+0

[this](http://www.precisejava.com/javaperf/j2se/StringAndStringBuffer.htm)は役に立つかもしれません – Anirudha

+0

もしあなたが 'StringBuilder'から' String'を構築したのであれば、おそらく別の文字列値の一部を追加するルーチン。 –

答えて

2

この出力から、一つのことは、文字列が同じ値を持っているならば、彼らは同じchar配列

はかなりない共有し、それに関係なく文字列が作成されたかの非常に明確である:これはあなたのために起こっています1つのリテラル文字列で開始し、そこから複数のインスタンスを作成します。 OpenJDK(Sun/Oracle)の実装では、文字列全体を表す場合、バッキング配列がコピーされます。 src.jar、またはここに表示されますhttp://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.%3Cinit%3E%28java.lang.String%29 異なる文字配列から始めるようにソース文字列を慎重に構築すると、バッキング配列を共有していないことがわかります。

今私の質問は、このchararrayが私の知る限り

に格納されている場合、リテラル文字列の文字配列はヒープ上に格納されている(クラスローディングの内部のより良い知識を持つものは、感じます自由にコメントすることができます)。ファイルからロードされる文字列は、 常には、そのバッキング配列をヒープに格納します。

私が確かに知っているのは、intern()が使用するデータ構造は、文字配列ではなくStringオブジェクトのみを参照していることです。列src

public String(String original) { 
     this.value = original.value; 
     this.hash = original.hash; 
    } 

から

+0

リテラルまたは新しいStringが実際にヒープに格納されているかどうかをJLS、Stringオブジェクトでチェックしました。文字列プールは単なる参照セットです。したがって、実際にはchar配列はヒープになります。 – Lokesh

1

最後に、「myTestString」というリテラルはインターネットされ、同じ値を持つすべてのinterned String参照は同じ物理Stringオブジェクトを参照します。したがって、リテラルはinternの結果としての完全一致文字列になります。

[修正]定義上、同一の文字シーケンス値を持つ2つの文字列のhashCode(ただしidentityHashCodeではなく)は同一です。

一方、char[]配列のhashCodeは、単にアドレスビットの不安定さであり、配列の内容とは関係ありません。これは、上記のすべての場合に、value配列がまったく同じ配列であることを示します。

(詳細は、char[]へのポインタ、オフセット、長さ、およびhashCode値を含む古い実装では、配列の要素0で始まるString値でオフセット値を非推奨にしました。他の(Sun以外の/ Oracle以外の)実装では、別のchar[]配列を使用せずに、メインのヒープ割り当て内にStringバイトを含めます。

[続き]テストケースといくつかの行を追加しました。 hashCodeとidentityHashCodeは、与えられたchar[]に同じ値を生成しますが、同じ内容の異なる配列では異なる値を生成します。

s1とs2で配列が同一であるという事実は、実質的には、内部のリテラル "myTestString"の配列char[]を共有しているためです。文字列が "新鮮な" char[]配列から別々に構築された場合、それらは異なるでしょう。

すべての主なテイクアウェイは、ストリングリテラルが中断され、ストリングがnew String(String)でコピーされたときにテストされるインプリメンテーションがソースの配列を「借り」ます。

Char array hash codes 
a1.hashCode() = 675303090 
a2.hashCode() = 367959235 
a1 identityHashCode = 675303090 
a2 identityHashCode = 367959235 
Strings from char arrays 
a1 String = ABCDE 
a1 String's hash = 62061635 
a1 String value's identityHashCode = 510044439 
a2 String = ABCDE 
a2 String's hash = 62061635 
a2 String value's identityHashCode = 1709651096 
+0

"定義上、String.hashCodeとSystem.identityHashCodeは同じ値を返します。" - これについてはリファレンスがありますか?それは確かに[docs](http://docs.oracle.com/javase/6/docs/api/java/lang/System.html#identityHashCode(java.lang.Object))とは言えないからです。 – parsifal

+0

@parsifal - OK、あなたはそこにいるのですが、仕様を少し誤解しました。 identityHashCodeはおそらく、ハッシュの "混乱したアドレス"バージョンを返し、したがって異なる(ただし「同一」の)オブジェクトを識別します。 –

+0

@HotLicks:出力を見ると、char配列のハッシュコードはすべての文字列で同じです。したがって、これは正しくありません "最初の2つの配列のハッシュが異なるのは驚くことではありません" – Lokesh

3

それは文字列は元の文字列で、このコンストラクタを共有char配列(値)で作成したことは明らかです。

これは、APIは、この共有を保証するものではないことに注意することが重要です。それは引数と同じ文字シーケンスを表すように

は、新しく作成されたStringオブジェクトを初期化します。つまり、新しく作成された文字列は引数文字列のコピーです。文字列が不変であるため、このコンストラクタの使用は不要です。

たとえば、元の文字列とchar配列を共有するために使用されるString.substringは、最新バージョンのJava 1.7 String.substring char配列のコピーを作成します。

+0

参考までに、元のサンプルはJavaの以前のバージョン(1.5、私は推測している)のものです。これは予期しないメモリ例外の*ロット*につながりました。これは、現在の(1.6/1.7)バージョンが、報告された文字列のサイズに対してバッキング配列のサイズを調べる理由です。 – parsifal

+0

@Evgeniy:これは、newを使って作成されたすべてのStringでchar配列が同じで、Stringリテラルと共有する理由を説明しています。このchar配列がヒープまたはpermgenでどこに作成されるかをテストする方法はありますか? – Lokesh

+1

str == str.intern()の場合、strがpermgenにあったことを意味します。 –

関連する問題