Javaの文字列定数プールはどこにありますか、ヒープまたはスタックですか?


103

文字列リテラルを処理するためにJVMが使用する定数プールと文字列定数プールの概念を知っています。しかし、JVMが文字列定数リテラルを格納するためにどのタイプのメモリを使用しているかはわかりません。スタックまたはヒープ?どのリテラルにも関連付けられていないリテラルなので、スタックに格納されると想定します。しかし、インスタンスによって参照されていない場合、リテラルはGC実行によって収集される必要があるため(間違っている場合は修正してください)、スタックに格納されている場合はどのように処理されますか?


11
プールをスタックに格納するにはどうすればよいですか?スタックの概念を知っていますか?
スクラムマイスター

1
こんにちはスクラムマイスター、私はそれができないことを意味しようとしました。間違った規則でごめんなさい。GCについてちょうど今知りました。それをありがとう
Rengasami Ramanujam

@TheScrumMeister-実際、状況によっては、ガベージコレクションされる可能性があります。「ディールブレーカー」とは、文字列リテラルに言及するクラスのコードオブジェクトが、リテラルを表すStringオブジェクトへの参照を持つことです。
スティーブンC

回答:


74

答えは技術的にはどちらでもありません。Java Virtual Machine仕様によると、文字列リテラルを格納するための領域は、ランタイム定数プールにあります。ランタイム定数プールのメモリ領域は、クラスごとまたはインターフェイスごとに割り当てられるため、オブジェクトインスタンスにまったく関連付けられていません。ランタイム定数プールはメソッド領域のサブセットであり、「クラスとインスタンスの初期化とインターフェースで使用される特別なメソッドを含む、ランタイム定数プール、フィールドとメソッドデータ、メソッドとコンストラクターのコードなどのクラスごとの構造を格納します。タイプの初期化」。VM仕様では、メソッド領域が 論理的にはヒープの一部ですが、メソッド領域に割り当てられたメモリが、ガベージコレクションや、ヒープに割り当てられた通常のデータ構造に関連付けられているその他の動作の影響を受けることはありません。


8
実際には、クラスがVMにロードされると、文字列定数はヒープ、VM全体の文字列プール(permgen内)にコピーされます。これは、異なるクラスの等しい文字列リテラルは、同じ文字列オブジェクト(JLSによる)。
–PaŭloEbermann、2011

1
ご回答ありがとうございます。私はこの議論で多くを理解しました。皆さんを知ってうれしいです:)
Rengasami Ramanujam

4
Paŭlo、これはSunの仮想マシンに当てはまりますが、JVMのすべての実装に当てはまるとは限りません。JVM仕様に記載されているように、ランタイム定数プールとメソッド領域は論理的にヒープの一部ですが、同じ動作である必要はありません。ほんのわずかな意味の違い、本当に:)
Duane Moore


54

この回答で説明されているように、文字列プールの正確な場所は指定されておらず、JVM実装によって異なる場合があります。

Java 7までは、プールはホットスポットJVMのヒープのpermgenスペースにありましたが、Java 7以降、ヒープの主要部分に移動されていることに注意してください。

エリア:HotSpot
概要:JDK 7では、インターンされた文字列は、Javaヒープの永続的な世代に割り当てられなくなりましたが、代わりに、Javaヒープのメイン部分(新旧世代と呼ばれます)とともに、他の世代とともに割り当てられます。アプリケーションによって作成されたオブジェクト。この変更により、メインのJavaヒープに存在するデータが多くなり、永続的な世代のデータが少なくなるため、ヒープサイズを調整する必要がある場合があります。ほとんどのアプリケーションは、この変更によりヒープ使用量の比較的小さな違いしか見えませんが、多くのクラスをロードしたり、String.intern()メソッドを多用する大きなアプリケーションでは、より大きな違いが見られます。RFE:6962931

また、Java 8 Hotspotでは、Permanent Generationが完全に削除されました。


30

文字列リテラルはスタックに格納されません。決して。実際、オブジェクトはスタックに格納されていません。

文字列リテラル(より正確には、それらを表すStringオブジェクト)、歴史的に「permgen」ヒープと呼ばれるヒープに格納されていました。(Permgenは、Permanent Generationの略です。)

通常の状況下では、文字列リテラルおよびpermgenヒープ内の他の多くのものは「永久に」到達可能であり、ガベージコレクションされません。(たとえば、文字列リテラルは、それらを使用するコードオブジェクトから常に到達可能です。)ただし、不要になった動的にロードされたクラスを見つけて収集するようにJVMを構成できます。これにより、文字列リテラルがガベージコレクションされる可能性があります。 。

明確化#1-私は、PermgenがGCされないわけではありません。これは通常、JVMがフルGCを実行することを決定したときに行われます。私のポイントは、文字列リテラルはそれらを使用するコードが到達可能である限り到達可能であり、コードのクラスローダーが到達可能である限りコードに到達可能であり、デフォルトのクラスローダーでは「永遠に」ということです。

明確化#2-実際、Java 7以降は通常のヒープを使用して文字列プールを保持しています。したがって、文字列リテラルとインターンされた文字列を表すStringオブジェクトは、実際には通常のヒープにあります。(詳細は@assyliasの回答を参照してください。)


しかし、文字列リテラルの格納とで作成された文字列の間の細い線を見つけようとしていますnew

「細い線」はありません。とてもシンプルです。

  • String 文字列リテラルを表す/対応するオブジェクトは、文字列プールに保持されます。
  • StringString::intern呼び出しによって作成されたオブジェクトは、文字列プールに保持されます。
  • 他のすべてのStringオブジェクトは文字列プールに保持されません。

次に、文字列プールがどこに「格納される」かという別の質問があります。Java 7より前は、permgenヒープでした。Java 7以降はメインヒープです。


23

文字列プーリング

文字列プーリング(文字列の正規化とも呼ばれる)は、複数の文字列オブジェクトを、値は同じであるがIDが異なる単一の共有文字列オブジェクトで置き換えるプロセスです。独自のマップを保持し(要件に応じてソフトまたは弱い参照で)、マップ値を正規化された値として使用することで、この目標を達成できます。または、JDKが提供するString.intern()メソッドを使用することもできます。

Java 6では、プーリングが制御不能になった場合にOutOfMemoryExceptionが発生する可能性が高いため、String.intern()を使用することは多くの標準で禁止されていました。文字列プールのOracle Java 7実装が大幅に変更されました。詳細については、http://bugs.sun.com/view_bug.do?bug_id=6962931および http://bugs.sun.com/view_bug.do?bug_id=6962930を参照してください 。

Java 6のString.intern()

古き良き時代には、すべてのインターンされた文字列はPermGenに保存されていました。これは主に、ロードされたクラスと文字列プールの保存に使用されるヒープの固定サイズ部分です。明示的にインターンされた文字列に加えて、PermGen文字列プールには、プログラムで以前に使用されたすべてのリテラル文字列も含まれています(ここで重要な単語が使用されます。

Java 6でのこのような文字列プールの最大の問題は、その場所、PermGenでした。PermGenのサイズは固定されており、実行時に拡張できません。-XX:MaxPermSize = 96mオプションを使用して設定できます。私の知る限りでは、デフォルトのPermGenサイズは、プラットフォームによって32Mから96Mの間で異なります。サイズを増やすことはできますが、サイズは固定されます。このような制限のため、String.internを非常に注意深く使用する必要がありました。このメソッドを使用して、制御されていないユーザー入力をインターンしない方がよいでしょう。そのため、Java 6での文字列プールは、主に手動で管理されたマップに実装されていました。

Java 7のString.intern()

Oracleエンジニアは、Java 7の文字列プーリングロジックに非常に重要な変更を加えました。文字列プールはヒープに再配置されました。つまり、個別の固定サイズのメモリ領域による制限を受けなくなります。これで、他のほとんどの通常のオブジェクトと同様に、すべての文字列がヒープ内に配置され、アプリケーションの調整中にヒープサイズのみを管理できるようになりました。技術的には、これだけでも、Java 7プログラムでString.intern()を使用することを再検討する十分な理由になる可能性があります。しかし、他の理由があります。

文字列プールの値はガベージコレクションされます

はい。JVM文字列プール内のすべての文字列は、プログラムルートから参照されていない場合、ガベージコレクションの対象になります。これは、説明されているすべてのバージョンのJavaに適用されます。これは、インターンされた文字列が範囲外になり、他の参照がない場合、JVM文字列プールからガベージコレクションされることを意味します。

ガベージコレクションの対象となり、ヒープに常駐するJVM文字列プールは、すべての文字列にとって適切な場所のようですね。理論的には真実です–未使用の文字列はプールからガベージコレクションされ、使用済みの文字列を使用すると、入力から同じ文字列を取得する場合に備えてメモリを節約できます。完璧なメモリ節約戦略のようですか?ほぼそうです。決定を行う前に、文字列プールがどのように実装されているかを知っておく必要があります。

ソース。


11

他の回答が説明するようにJavaのメモリは2つの部分に分かれています

1.スタック:スレッドごとに1つのスタックが作成され、スタックフレームを格納します。スタックフレームはローカル変数を格納し、変数が参照型の場合、その変数は実際のオブジェクトのヒープ内のメモリ位置を参照します。

2.ヒープ:すべての種類のオブジェクトはヒープのみで作成されます。

ヒープメモリは再び3つの部分に分かれています

1.ヤングジェネレーション:寿命の短いオブジェクトを保存します。ヤングジェネレーション自体は、エデンスペースサバイバースペースの 2つのカテゴリに分類できます。

2. Old Generation:多くのガベージコレクションサイクルを生き延び、まだ参照されているオブジェクトを格納します。

3.永続的な生成:ランタイム定数プールなど、プログラムに関するメタデータを保存します。

文字列定数プールは、ヒープメモリの永続的な生成領域に属しています。

javap -verbose class_nameメソッドの参照(#Methodref)、クラスオブジェクト(#Class)、文字列リテラル(#String)を表示することで、バイトコード内のコードのランタイム定数プールを確認できます。

ランタイム定数プール

詳細については、私の記事「JVMによるメソッドのオーバーロードとオーバーライドの内部処理」を参照してください。


所属を開示し、投稿を通じてサイトを宣伝する手段としてサイトを使用しないでください。参照してください。私は良い答えを書くにはどうすればよいですか?

9

すでにここに含まれている素晴らしい答えに、私の見方で欠けているもの、つまりイラストを追加したいと思います。

すでにJVMは、Javaプログラムに割り当てられたメモリを2つの部分に分割します。1つはスタックで、もう1つはヒープです。スタックは実行目的で使用され、ヒープはストレージ目的で使用されます。そのヒープメモリでは、JVMは文字列リテラル専用のメモリを割り当てます。ヒープメモリのこの部分は、文字列定数プールと呼ばれます

たとえば、次のオブジェクトを初期化するとします。

String s1 = "abc"; 
String s2 = "123";
String obj1 = new String("abc");
String obj2 = new String("def");
String obj3 = new String("456);

文字列リテラルはs1s2文字列定数プール、オブジェクトobj1、obj2、obj3をヒープに移動します。それらのすべては、スタックから参照されます。

また、「abc」はヒープおよび文字列定数プールに表示されることに注意してください。なぜString s1 = "abc"そしてString obj1 = new String("abc")、このようにして作成されますか?これは、StringオブジェクトのString obj1 = new String("abc")参照として区別される新しいインスタンスを明示的に作成し、String s1 = "abc"利用可能な場合は文字列定数プールからインスタンスを再利用する可能性があるためです。より詳細な説明については、https//stackoverflow.com/a/3298542/2811258

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


与えられた図では、リテラル「def」と「456」はどこに存在しますか。そして、これらはどのように参照されますか?
Satyendra 2017

コメント@Satyendraに感謝します。イラストと回答を更新しました。
ジョニー

@別の文字列オブジェクト「abc」が作成される理由..それは、リテラルを指すために参照obj1を使用する必要がありますか?

これは、String obj1 = new String( "abc")がStringオブジェクトの新しい参照インスタンスを明示的に作成し、String s1 = "abc"が使用可能な場合、文字列定数プールのインスタンスを再利用できるためです。より詳細な説明については、stackoverflow.com
Johnny
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.