Javaのスタックメモリとヒープメモリ


99

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

クラスがあると仮定します

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

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

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


1
stackoverflow.com/questions/1056444/is-it-on-the-stack-or-heapはあなたの質問に答えているようです。
S.Lott

@ S.Lott。ただし、これはCでなくJavaについてです。
PéterTörök11年

@PéterTörök:同意しました。コードサンプルはJavaですが、Javaのみであることを示すタグはありません。また、一般原則はCと同様にJavaにも適用する必要があります。さらに、スタックオーバーフローに関するこの質問には多くの答えがあります。
S.Lott

9
@SteveHaigh:このサイトでは、誰もが何かがここに属しているかどうかについてあまりにも心配しています...質問がここに属しているかどうかについて、このサイトは本当に気が狂っていますか?
サムゴールドバーグ14年

回答:


106

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

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

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

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


2
「そして参照のみ(順番にプリミティブ)」参照がプリミティブだと言うのはなぜですか?はっきりさせてください。
オタク

5
@Geek:プリミティブデータ型の一般的な定義が適用されるため:「プログラミング言語によって基本的なビルディングブロックとして提供されるデータ型」。また、参考文献が記事のさらに下の標準的な例の中にリストされていることに気付くかもしれません。
back2dos

4
@Geek:データの観点から、参照を含むプリミティブデータ型のいずれかを数値として表示できます。でも、charsが数字であり、ように交換可能に使用することができます。参照は、32または64ビット長のメモリアドレスを参照する単なる数字でもあります(ただし、それらをそのまま使用することはできません-をいじっていない限りsun.misc.Unsafe)。
スネラスムッセン

3
この回答の用語は間違っています。Java言語仕様によれば、参照はプリミティブではありません。しかし、答えが言っていることの要点は正しいです。(。あなたは参照が「ある意味で」という議論をすることができますがプリミティブは、JLSは、Javaのための用語を定義することによってであり、そしてそれがプリミティブ型であることを言うbooleanbyteshortcharintlongfloatdouble。)
スティーブン・C

4
この記事で見つけたように、Java オブジェクトをスタックに(または、短命のオブジェクトのレジスターにさえ)保管する場合があります。JVMは非常に多くのことをカバーしています。「現在、Javaはスタックにのみプリミティブを格納する」と言うのは正確ではありません。

49

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

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

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

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

論文の結論は次のとおりです。

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

したがって、次のようなコードがある場合:

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

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

ウィキペディアでのエスケープ分析の詳細。論文掘り下げることをいとわない人のために、Java用のエスケープ解析 IBMから。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ファイルを挿入するとどうなりますか。各呼び出しは、巨大な文字列全体をスタックにコピーします- それは非常に無駄です。

代わりに、即時スコープの外にある寿命を持つオブジェクト(別のスコープに渡される、他の誰かが維持している構造にスタックするなど)をヒープと呼ばれる別の領域に配置する方が適切です。

スタック上

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

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

これにより、コードのランタイムを作成する人がスタックとヒープを使用するのが非常に簡単になります。それらは単に、コードで作業するための多くの概念と方法であり、言語でコードを書く人がそれらを明示的に考えることから解放されます。

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

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

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

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

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

なぜこれが重要なのですか?

ほとんどの場合、その伝統。書かれた教科書、コンパイラクラス、およびさまざまなビットのドキュメントは、ヒープとスタックについて大いに役立ちます。

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

オブジェクトはどこかにあり、そのオブジェクトが存在する適切な時間の間、正確かつ迅速にアクセスできる場所にあります。スタックまたはヒープ上にある場合-それは実際には問題ではありません。


7
  1. スタック内のポインターによって参照されるオブジェクトの一部としてのヒープ内。すなわち。aとbは互いに隣接して保存されます。
  2. すべてのメモリがスタックメモリである場合、それはもはや効率的ではないからです。開始する場所に小さな高速アクセス領域を用意し、残りのメモリのはるかに大きな領域にその参照項目を配置するとよいでしょう。ただし、これは、オブジェクトが単に単一のプリミティブであり、スタックへのポインタとほぼ同じ量のスペースを占有する場合、過剰になります。
  3. はい。

1
スタックにオブジェクトを格納した場合(数十万のエントリを持つディクショナリオブジェクトを想像している場合)、オブジェクトをコピーする必要がある関数に渡したり、関数から返すために、ポイント2に追加します。時間。ヒープ内のオブジェクトへのポインターまたは参照を使用して、(小さな)参照のみを渡します。
スコットホイットロック

1
オブジェクトがガベージコレクションされている場合、それを指す参照がスタックにないため、3は「いいえ」だと思いました。
ルチアーノ

@ルチアーノ-あなたの主張がわかります。質問3の読み方が異なります。「同時に」または「その時間までに」は暗黙的です。:: shrug ::
pdr

3
  1. ヒープ上で、Javaがエスケープ上でセマンティクスに影響しないことを証明した後、最適化としてスタック上のクラスインスタンスを割り当てない限り。ただし、これは実装の詳細であるため、マイクロ最適化を除くすべての実用的な目的では、答えは「ヒープ上」です。

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

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


3

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

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

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

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

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

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

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

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


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.