Javaのスタックメモリとヒープスペース
1. 序章
アプリケーションを最適な方法で実行するために、JVMはメモリをスタックメモリとヒープメモリに分割します。 新しい変数とオブジェクトを宣言したり、新しいメソッドを呼び出したり、文字列を宣言したり、または同様の操作を実行したりするたびに、JVMはスタックメモリまたはヒープスペースのいずれかからこれらの操作にメモリを指定します。
このチュートリアルでは、これらのメモリモデルを調べます。 まず、それらの主な機能について説明します。 次に、それらがRAMにどのように格納され、どこで使用されるかを学習します。 最後に、それらの主な違いについて説明します。
2. Javaのスタックメモリ
Javaのスタックメモリは、静的メモリの割り当てとスレッドの実行に使用されます。メソッドに固有のプリミティブ値と、ヒープ内にあるメソッドから参照されるオブジェクトへの参照が含まれます。
このメモリへのアクセスは後入れ先出し(LIFO)の順序で行われます。 新しいメソッドを呼び出すたびに、プリミティブ変数やオブジェクトへの参照など、そのメソッドに固有の値を含む新しいブロックがスタックの一番上に作成されます。
メソッドの実行が終了すると、対応するスタックフレームがフラッシュされ、フローは呼び出し元のメソッドに戻り、次のメソッドに使用できるスペースができます。
2.1. スタックメモリの主な機能
スタックメモリの他の機能には、次のものがあります。
- 新しいメソッドが呼び出されて返されると、それぞれ拡大および縮小します。
- スタック内の変数は、それらを作成したメソッドが実行されている間のみ存在します。
- メソッドの実行が終了すると、自動的に割り当てと割り当て解除が行われます。
- このメモリがいっぱいの場合、Javaはjava.lang.StackOverFlowError。をスローします
- このメモリへのアクセスは、ヒープメモリと比較すると高速です。
- 各スレッドは独自のスタックで動作するため、このメモリはスレッドセーフです。
3. Javaのヒープスペース
ヒープスペースは、実行時でのJavaオブジェクトとJREクラスの動的メモリ割り当てに使用されます。 新しいオブジェクトは常にヒープスペースに作成され、これらのオブジェクトへの参照はスタックメモリに保存されます。
これらのオブジェクトにはグローバルアクセスがあり、アプリケーションのどこからでもアクセスできます。
このメモリモデルを世代と呼ばれる小さな部分に分割できます。これは次のとおりです。
- 若い世代– これは、すべての新しいオブジェクトが割り当てられ、エージングされる場所です。 これがいっぱいになると、マイナーなガベージコレクションが発生します。
- 古い世代または古い世代– これは、長期間存続するオブジェクトが格納される場所です。 オブジェクトが若い世代に格納されると、オブジェクトの年齢のしきい値が設定され、そのしきい値に達すると、オブジェクトは古い世代に移動されます。
- 永続的な生成– これは、ランタイムクラスとアプリケーションメソッドのJVMメタデータで構成されます。
これらの異なる部分については、記事 JVM、JRE、およびJDKの違いでも説明されています。
要件に応じて、ヒープメモリのサイズをいつでも操作できます。 詳細については、このリンクされたBaeldungの記事をご覧ください。
3.1. Javaヒープメモリの主な機能
ヒープスペースの他の機能には、次のものがあります。
- これには、若い世代、古い世代または古い世代、永続的な世代などの複雑なメモリ管理手法を介してアクセスします。
- ヒープスペースがいっぱいの場合、Javaはjava.lang.OutOfMemoryError。をスローします
- このメモリへのアクセスは、スタックメモリよりも比較的低速です。
- このメモリは、スタックとは対照的に、自動的に割り当てが解除されません。 メモリ使用の効率を維持するために、未使用のオブジェクトを解放するためにガベージコレクタが必要です。
- スタックとは異なり、ヒープはスレッドセーフではないため、コードを適切に同期して保護する必要があります。
4. 例
これまでに学んだことに基づいて、単純なJavaコードを分析して、ここでメモリを管理する方法を評価しましょう。
class Person {
int id;
String name;
public Person(int id, String name) {
this.id = id;
this.name = name;
}
}
public class PersonBuilder {
private static Person buildPerson(int id, String name) {
return new Person(id, name);
}
public static void main(String[] args) {
int id = 23;
String name = "John";
Person person = null;
person = buildPerson(id, name);
}
}
これを段階的に分析してみましょう。
- main()メソッドに入ると、このメソッドのプリミティブと参照を格納するためにスタックメモリにスペースが作成されます。
- スタックメモリは、整数idのプリミティブ値を直接格納します。
- タイプPersonの参照変数personもスタックメモリに作成され、ヒープ内の実際のオブジェクトを指します。
- main()からパラメーター化されたコンストラクター Person(int、String)を呼び出すと、前のスタックの上にさらにメモリが割り当てられます。 これは保存します:
- スタックメモリ内の呼び出し元オブジェクトのthisオブジェクト参照
- スタックメモリのプリミティブ値id
- String引数name、の参照変数。これは、ヒープメモリ内の文字列プールからの実際の文字列を指します。
- main メソッドは、 buildPerson()静的メソッドをさらに呼び出します。このメソッドに対して、前のメソッドの上にスタックメモリでさらに割り当てが行われます。 これにより、上記の方法で変数が再び保存されます。
- ただし、ヒープメモリには、タイプPerson。の新しく作成されたオブジェクトpersonのすべてのインスタンス変数が格納されます。
次の図でこの割り当てを見てみましょう。
5. 概要
この記事を締めくくる前に、スタックメモリとヒープスペースの違いを簡単に要約しましょう。
パラメータ | スタックメモリ | ヒープスペース |
---|---|---|
応用 | スタックは、スレッドの実行中に一度に1つずつ部分的に使用されます | アプリケーション全体が実行時にヒープスペースを使用します |
サイズ | スタックにはOSに応じたサイズ制限があり、通常はヒープよりも小さくなります | ヒープにサイズ制限はありません |
保管所 | ヒープスペースで作成されたプリミティブ変数とオブジェクトへの参照のみを格納します | 新しく作成されたすべてのオブジェクトがここに保存されます |
注文 | 後入れ先出し(LIFO)メモリ割り当てシステムを使用してアクセスします | このメモリには、若い世代、古い世代または古い世代、永続的な世代などの複雑なメモリ管理手法を介してアクセスします。 |
人生 | スタックメモリは、現在のメソッドが実行されている間のみ存在します | アプリケーションが実行されている限り、ヒープスペースは存在します |
効率 | ヒープと比較すると、割り当てがはるかに高速です | スタックと比較すると、割り当てが遅い |
割り当て/割り当て解除 | このメモリは、メソッドが呼び出されて返されるときに、それぞれ自動的に割り当てられ、割り当てが解除されます。 | ヒープスペースは、新しいオブジェクトが作成され、参照されなくなったときにGargabeCollectorによって割り当てが解除されたときに割り当てられます。 |
6. 結論
スタックとヒープは、Javaがメモリを割り当てる2つの方法です。 この記事では、それらがどのように機能するか、そしてより良いJavaプログラムを開発するためにそれらをいつ使用するかを学びました。
Javaでのメモリ管理の詳細については、この記事を参照してください。 また、JVMガベージコレクターについても触れました。これについては、この記事で簡単に説明されています。