プリミティブフィールドはどこに保存されますか?
プリミティブフィールドは、どこかにインスタンス化されるオブジェクトの一部として保存されます。これがどこにあるかを考える最も簡単な方法は、ヒープです。 ただし、これは常にそうではありません。Javaの理論と実践:アーバンパフォーマンスの伝説で説明したように、再訪:
JVMは、エスケープ分析と呼ばれる手法を使用できます。これにより、特定のオブジェクトが存続期間全体にわたって単一のスレッドに制限されたままであり、その存続期間が特定のスタックフレームの存続期間によって制限されていることがわかります。このようなオブジェクトは、ヒープではなくスタックに安全に割り当てることができます。さらに良いことに、小さなオブジェクトの場合、JVMは割り当てを完全に最適化し、オブジェクトのフィールドをレジスタに単純に引き上げることができます。
したがって、「オブジェクトが作成され、フィールドもそこにある」と言うだけでなく、何かがヒープ上にあるのかスタック上にあるのかはわかりません。小さく、短命のオブジェクトの場合、「オブジェクト」がメモリに存在せず、代わりにフィールドがレジスタに直接配置される可能性があることに注意してください。
論文の結論は次のとおりです。
JVMは、開発者だけが知ることができると想定していたものを理解するのに驚くほど優れています。JVMがスタック割り当てとヒープ割り当てをケースバイケースで選択できるようにすることで、スタックに割り当てるかヒープに割り当てるかをプログラマに悩ませることなく、スタック割り当てのパフォーマンス上の利点を得ることができます。
したがって、次のようなコードがある場合:
void foo(int arg) {
Bar qux = new Bar(arg);
...
}
がそのスコープを離れることを...
許可しない場合、代わりにスタックに割り当てられます。これは実際にはVMにとって勝利です。これは、ガベージコレクションを行う必要がないことを意味します。スコープを離れると消えます。qux
qux
ウィキペディアでのエスケープ分析の詳細。論文掘り下げることをいとわない人のために、Java用のエスケープ解析 IBMから。C#の世界から来た人には、The Stack Is An Implementation DetailとThe Truth About Value Types by Eric Lippertの良い読み物があります(Javaの型にも多くの概念と側面が同じか類似しているので便利です) 。.Netブックがスタックとヒープメモリの割り当てについて説明しているのはなぜですか?これにも入ります。
スタックとヒープの理由について
ヒープ上
それでは、なぜスタックまたはヒープがあるのでしょうか?スコープから外れるものについては、スタックは高価になる可能性があります。コードを考慮してください:
void foo(String arg) {
bar(arg);
...
}
void bar(String arg) {
qux(arg);
...
}
void qux(String arg) {
...
}
パラメーターもスタックの一部です。ヒープがない状況では、スタック上のすべての値を渡すことになります。これは"foo"
、小さな文字列には適していますが、その文字列に巨大なXMLファイルを挿入するとどうなりますか。各呼び出しは、巨大な文字列全体をスタックにコピーします- それは非常に無駄です。
代わりに、即時スコープの外にある寿命を持つオブジェクト(別のスコープに渡される、他の誰かが維持している構造にスタックするなど)をヒープと呼ばれる別の領域に配置する方が適切です。
スタック上
スタックは必要ありません。仮に、(任意の深さの)スタックを使用しない言語を書くことができます。私が若いときに学んだ古いBASICはそうしていました。8レベルのgosub
呼び出ししか行えず、すべての変数はグローバルでした。スタックはありませんでした。
スタックの利点は、スコープに存在する変数がある場合、そのスコープを離れると、そのスタックフレームがポップされることです。そこにあるものとないものを本当に単純化します。プログラムは別の手順、新しいスタックフレームに移動します。プログラムはプロシージャに戻り、現在のスコープが表示されているプロシージャに戻ります。プログラムはプロシージャを終了し、スタック上のすべてのアイテムの割り当てが解除されます。
これにより、コードのランタイムを作成する人がスタックとヒープを使用するのが非常に簡単になります。それらは単に、コードで作業するための多くの概念と方法であり、言語でコードを書く人がそれらを明示的に考えることから解放されます。
スタックの性質は、断片化できないことも意味します。メモリの断片化は、ヒープの本当の問題です。いくつかのオブジェクトを割り当て、次に中央のオブジェクトをガベージコレクションしてから、次に割り当てる大きなオブジェクト用のスペースを見つけてみます。その混乱。代わりに物をスタックに置くことができるということは、それを処理する必要がないことを意味します。
何かがガベージコレクトされたとき
何かがガベージコレクトされると、消えてしまいます。しかし、それは既に忘れられているため、ガベージコレクションのみです。プログラムの現在の状態からアクセスできるプログラム内のオブジェクトへの参照はもうありません。
これは、ガベージコレクションの非常に大きな簡略化であることを指摘します。多くのガベージコレクターがあります(Java内でも-さまざまなフラグ(docs)を使用してガベージコレクターを調整できます。これらは異なる動作をし、それぞれがどのように動作するかのニュアンスはこの回答には少し深すぎます。Java Garbage Collection Basicsを使用して、その一部がどのように機能するかをよりよく理解してください。
つまり、スタックに何かが割り当てられている場合、その一部としてガベージコレクションは行われませんSystem.gc()
。スタックフレームがポップしたときに割り当てが解除されます。ヒープ上に何かがあり、スタック上の何かから参照されている場合、その時点でガベージコレクションは行われません。
なぜこれが重要なのですか?
ほとんどの場合、その伝統。書かれた教科書、コンパイラクラス、およびさまざまなビットのドキュメントは、ヒープとスタックについて大いに役立ちます。
ただし、今日の仮想マシン(JVMなど)は、これをプログラマから隠そうとするためにかなりの時間を費やしています。どちらか一方が不足していて、理由を知る必要がない限り(ストレージスペースを適切に増やすのではなく)、それはそれほど重要ではありません。
オブジェクトはどこかにあり、そのオブジェクトが存在する適切な時間の間、正確かつ迅速にアクセスできる場所にあります。スタックまたはヒープ上にある場合-それは実際には問題ではありません。