web-dev-qa-db-ja.com

Javaのスタックおよびヒープメモリ

私が理解しているように、Javaでは、スタックメモリはプリミティブとメソッドの呼び出しを保持し、ヒープメモリはオブジェクトの格納に使用されます。

クラスがあるとします

class A {
       int a ;
       String b;
       //getters and setters
}
  1. クラスaのプリミティブAはどこに保存されますか?

  2. なぜヒープメモリが存在するのですか?スタックにすべてを格納できないのはなぜですか?

  3. オブジェクトがガベージコレクションされると、オブジェクトに関連付けられたスタックは破棄されますか?

101

スタックとヒープの基本的な違いは、値のライフサイクルです。

スタック値は、それらが作成された関数のスコープ内にのみ存在します。スタック値が返されると、それらは破棄されます。
ただし、ヒープ値はヒープ上に存在します。それらは、ある時点で作成され、別の時点で破棄されます(言語/ランタイムに応じて、GCまたは手動で)。

Javaは、スタックにプリミティブのみを保存します。これにより、スタックが小さく保たれ、個々のスタックフレームが小さく保たれるため、より多くのネストされた呼び出しが可能になります。
オブジェクトはヒープ上に作成され、参照(つまりプリミティブ)のみがスタックで渡されます。

したがって、オブジェクトを作成すると、オブジェクトに属するすべての変数とともにオブジェクトがヒープに置かれるため、関数呼び出しが戻った後もオブジェクトを保持できます。

106
back2dos

プリミティブフィールドはどこに保存されますか?

プリミティブフィールドは、インスタンス化されたオブジェクトの一部として保存されますどこか。これがどこにあるかを考える最も簡単な方法は、ヒープです。 ただし、これは常に当てはまるわけではありません。 Javaの理論と実践:都市のパフォーマンスの伝説、 で説明されているように:

JVMは、エスケープ分析と呼ばれる手法を使用できます。これにより、特定のオブジェクトが存続期間全体にわたって単一のスレッドに限定されたままであり、その存続期間は特定のスタックフレームの存続期間によって制限されます。このようなオブジェクトは、ヒープではなくスタックに安全に割り当てることができます。さらに良いことに、小さなオブジェクトの場合、JVMは割り当てを完全に最適化し、オブジェクトのフィールドをレジスターに引き上げることができます。

したがって、「オブジェクトが作成され、フィールドもそこにある」と言うだけでなく、何かがヒープ上にあるのかスタック上にあるのかはわかりません。小さくて寿命の短いオブジェクトの場合、「オブジェクト」がメモリに存在せず、そのフィールドがレジスタに直接配置される可能性があることに注意してください。

論文は次のように締めくくられます。

JVMは驚くべきことに、開発者だけが知っていると想定していたものを理解するのに優れています。 JVMにケースバイケースでスタック割り当てとヒープ割り当てのどちらかを選択させることで、プログラマーにスタックに割り当てるかヒープに割り当てるかを悩ませることなく、スタック割り当てのパフォーマンス上の利点を得ることができます。

したがって、次のようなコードがあるとします。

_void foo(int arg) {
    Bar qux = new Bar(arg);
    ...
}
_

_..._はquxがそのスコープを離れることを許可しない場合、quxmayが割り当てられます代わりにスタック。これは実際にはVMの勝利です。これは、ガベージコレクションを行う必要がないことを意味します。スコープを離れると消えます。

ウィキペディアの エスケープ分析 の詳細。論文を詳しく知りたい方は、IBMの Escape Analysis for Java をご利用ください。 C#の世界から来る人には、 The Stack Is an Implementation DetailThe 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ファイルを挿入するとどうなりますか。各呼び出しは、巨大な文字列全体をスタックにコピーします-thatは非常に無駄です。

代わりに、直接スコープの外にあるライフ(別のスコープに渡された、他の誰かが維持している構造でスタックされた、など)を持つオブジェクトを、ヒープと呼ばれる別の領域に配置することをお勧めします。

スタック上

スタックは必要ありませんスタック。仮に、スタックを使用しない言語(任意の深さ)を書くことができます。私が青年期に学んだ古いBASICはそうしました、gosub呼び出しの8レベルしか実行できず、すべての変数はグローバルでした-スタックがありませんでした。

スタックの利点は、スコープとともに存在する変数がある場合、そのスコープを離れると、そのスタックフレームがポップされることです。存在するものと存在しないものを単純化します。プログラムは別のプロシージャ、新しいスタックフレームに移動します。プログラムがプロシージャに戻り、現在のスコープが表示されているプロシージャに戻ります。プログラムはプロシージャを離れ、スタック上のすべてのアイテムが割り当て解除されます。

これにより、スタックとヒープを使用するコードのランタイムを作成する人が本当に楽になります。それらは単にコードに取り組む多くの概念と方法であり、言語でコードを書く人がそれらを明示的に考えることから解放されることを可能にします。

スタックの性質は、断片化できないことも意味します。 メモリの断片化 はヒープの実際の問題です。いくつかのオブジェクトを割り当ててから、真ん中のオブジェクトをガベージコレクションしてから、次に大きいオブジェクトを割り当てるためのスペースを見つけます。その混乱。代わりにスタックに物を置くことができるということは、それに対処する必要がないことを意味します。

ガベージコレクトされたとき

何かがガベージコレクションされると、それはなくなります。しかし、それはすでに忘れられているため、ガベージコレクションのみです。プログラムの現在の状態からアクセスできるプログラム内のオブジェクトへの参照はありません。

これはガベージコレクションの非常に大きな単純化であることを指摘しておきます。多くのガベージコレクタがあります(Java内でも)-さまざまなフラグ( docs を使用してガベージコレクタを微調整できます)。これらの動作は異なり、ニュアンスも異なります。それぞれがこの答えには深すぎるので、 Java Garbage Collection Basics を読んで、それがどのように機能するかをよりよく理解することができます。

とは言っても、スタックに何かが割り当てられた場合、System.gc()の一部としてガベージコレクションは行われません。スタックフレームがポップすると、割り当てが解除されます。ヒープ上に何かがあり、スタック上の何かから参照されている場合、その時点ではガベージコレクションされません。

なぜこれが問題なのですか?

ほとんどの場合、その伝統。書かれたテキストブックとコンパイラクラス、およびさまざまなビットのドキュメントは、ヒープとスタックについて重要な役割を果たします。

しかし、今日の仮想マシン(JVMなど)は、これをプログラマーから隠そうとするために多大な時間を費やしています。どちらかが不足していて、理由を知る必要がある場合を除いて(ストレージ領域を適切に増やすだけでなく)、それも重要ではありませんたくさん。

オブジェクトはsomewhereであり、存在する適切な時間の間、正しく迅速にアクセスできる場所にあります。スタック上またはヒープ上にある場合-それは本当に問題ではありません。

49
user40980
  1. ヒープ内で、オブジェクトの一部として。スタック内のポインターによって参照されます。すなわち。 aとbは互いに隣接して格納されます。
  2. すべてのメモリがスタックメモリである場合、それはもはや効率的ではありません。最初に小さな高速アクセス領域を用意し、それよりもはるかに広いメモリ領域に参照アイテムを置くとよいでしょう。ただし、オブジェクトへのポインタと同じくらいスタック上のスペースを占める単一のプリミティブであるオブジェクトの場合、これはやりすぎです。
  3. はい。
7
pdr
  1. Java=でない限りヒープ上でエスケープ分析を介してこれがセマンティクスに影響しないことを証明した後、最適化としてクラスインスタンスをスタックに割り当てます。ただし、これは実装の詳細であるため、マイクロ最適化の答えは「ヒープ上」です。

  2. スタックメモリは、先入れ先出しで最後に割り当ておよび割り当て解除する必要があります。ヒープメモリは、任意の順序で割り当ておよび割り当て解除できます。

  3. オブジェクトがガベージコレクションされると、スタックからそのオブジェクトを指す参照はなくなります。あったとしても、オブジェクトを存続させます。スタックプリミティブは、関数が戻るときに自動的に破棄されるため、ガベージコレクションはまったく行われません。

3
dsimcha

スタックメモリは、ローカル変数と関数呼び出しを格納するために使用されます。

ヒープメモリは、Javaでオブジェクトを格納するために使用されます。とにかく、オブジェクトはコードのどこに作成されます。

class Aのプリミティブaはどこに保存されますか?

この場合、プリミティブaはクラスAオブジェクトに関連付けられています。したがって、ヒープメモリに作成されます。

なぜヒープメモリが存在するのですか?スタックにすべてを格納できないのはなぜですか?

  • スタック上に作成された変数はスコープ外になり、自動的に破棄されます。
  • スタックは、ヒープ上の変数に比べて割り当てがはるかに高速です。
  • ヒープ上の変数は、ガベージコレクターによって破棄する必要があります。
  • スタック上の変数に比べて割り当てが遅くなります。
  • コンパイル時間の前に割り当てる必要があるデータの量が正確にわかっていて、それが大きすぎない場合は、スタックを使用します(プリミティブローカル変数はスタックに格納されます)。
  • 実行時に必要なデータの量が正確にわからない場合、または大量のデータを割り当てる必要がある場合は、ヒープを使用します。

オブジェクトがガベージコレクションされると、オブジェクトに関連付けられたスタックは破棄されますか?

ガベージコレクターはヒープメモリのスコープの下で機能するため、ルートからルートへの参照チェーンを持たないオブジェクトを破棄します。

3
Premraj