JVM のメモリを理解する
すでにご存知のとおり、JVM は内部で Java プログラムを実行します。他の仮想マシンと同様に、仮想マシンには独自のメモリ編成システムがあります。
内部メモリのレイアウトは、Java アプリケーションがどのように動作するかを示します。このようにして、アプリケーションとアルゴリズムの動作におけるボトルネックを特定できます。どのように機能するかを見てみましょう。
重要!元の Java モデルは十分ではなかったので、Java 1.5 で改訂されました。このバージョンは現在も使用されています (Java 14+)。
スレッドスタック
JVM によって内部的に使用される Java メモリ モデルは、メモリをスレッド スタックとヒープに分割します。論理的にブロックに分割された Java メモリ モデルを見てみましょう。
JVM で実行されているすべてのスレッドには独自のスタックがあります。スタックには、スレッドがどのメソッドを呼び出したかに関する情報が保持されます。これを「コールスタック」と呼びます。コールスタックは、スレッドがコードを実行するとすぐに再開されます。
スレッドのスタックには、スレッドのスタック上でメソッドを実行するために必要なすべてのローカル変数が含まれています。スレッドは自身のスタックにのみアクセスできます。ローカル変数は他のスレッドには表示されず、ローカル変数を作成したスレッドのみに表示されます。2 つのスレッドが同じコードを実行している状況では、両方のスレッドが独自のローカル変数を作成します。したがって、各スレッドは各ローカル変数の独自のバージョンを持ちます。
プリミティブ型 ( boolean、byte、short、char、int、long、float、double ) のすべてのローカル変数は完全にスレッド スタックに格納され、他のスレッドには表示されません。あるスレッドはプリミティブ変数のコピーを別のスレッドに渡すことができますが、プリミティブ ローカル変数を共有することはできません。
ヒープ
ヒープには、オブジェクトを作成したスレッドに関係なく、アプリケーションで作成されたすべてのオブジェクトが含まれます。これには、プリミティブ型 ( Byte、Integer、Longなど) のラッパーが含まれます。オブジェクトが作成されてローカル変数に割り当てられたか、別のオブジェクトのメンバー変数として作成されたかは関係なく、オブジェクトはヒープに保存されます。
以下は、コール スタックとローカル変数 (スタックに保存される)、およびオブジェクト (ヒープに保存される) を示す図です。
ローカル変数がプリミティブ型の場合、スレッドのスタックに格納されます。
ローカル変数はオブジェクトへの参照にすることもできます。この場合、参照 (ローカル変数) はスレッド スタックに格納されますが、オブジェクト自体はヒープに格納されます。
オブジェクトにはメソッドが含まれており、これらのメソッドにはローカル変数が含まれます。これらのローカル変数は、メソッドを所有するオブジェクトがヒープに格納されている場合でも、スレッド スタックにも格納されます。
オブジェクトのメンバー変数は、オブジェクト自体とともにヒープに保存されます。これは、メンバー変数がプリミティブ型の場合とオブジェクト参照の場合の両方に当てはまります。
静的クラス変数もクラス定義とともにヒープに保存されます。
オブジェクトとのインタラクション
ヒープ上のオブジェクトには、そのオブジェクトへの参照を持つすべてのスレッドからアクセスできます。スレッドがオブジェクトにアクセスできる場合、そのオブジェクトの変数にアクセスできます。2 つのスレッドが同じオブジェクトのメソッドを同時に呼び出すと、どちらもオブジェクトのメンバー変数にアクセスできますが、各スレッドはローカル変数の独自のコピーを持ちます。
2 つのスレッドにはローカル変数のセットがあります。ローカル変数 2ヒープ上の共有オブジェクトを指します (オブジェクト 3)。各スレッドには、独自の参照を持つローカル変数の独自のコピーがあります。それらの参照はローカル変数であるため、スレッド スタックに保存されます。ただし、2 つの異なる参照がヒープ上の同じオブジェクトを指しています。
一般的なものであることに注意してくださいオブジェクト 3へのリンクがありますオブジェクト 2とオブジェクト 4メンバー変数として (矢印で示されています)。これらのリンクを通じて、2 つのスレッドがアクセスできます。オブジェクト 2と物体4.
この図には、ローカル変数 (ローカル変数 1MethodTwoから)。その各コピーには、2 つの異なるオブジェクト (オブジェクト 1とオブジェクト 5)同じものではありません。理論的には、両方のスレッドが両方にアクセスできます。オブジェクト 1、 するオブジェクト 5これらのオブジェクトの両方への参照がある場合。ただし、上の図では、各スレッドは 2 つのオブジェクトのうちの 1 つへの参照しか持っていません。
オブジェクトとのインタラクションの例
コードで動作を実証する方法を見てみましょう。
public class MySomeRunnable implements Runnable() {
public void run() {
one();
}
public void one() {
int localOne = 1;
Shared localTwo = Shared.instance;
//... do something with local variables
two();
}
public void two() {
Integer localOne = 2;
//... do something with local variables
}
}
public class Shared {
// store an instance of our object in a variable
public static final Shared instance = new Shared();
// member variables pointing to two objects on the heap
public Integer object2 = new Integer(22);
public Integer object4 = new Integer(44);
}
run()メソッドはone()メソッドを呼び出し、次にone() はtwo()を呼び出します。
one()メソッドは、プリミティブなローカル変数 (ローカルワンint型のローカル変数 (ローカルツー)、これはオブジェクトへの参照です。
one()メソッドを実行する各スレッドは独自のコピーを作成します。ローカルワンとローカルツーあなたのスタックに。変数ローカルワンは互いに完全に分離され、各スレッドのスタック上に存在します。あるスレッドは、別のスレッドがそのコピーに加えた変更を確認できませんローカルワン。
one()メソッドを実行する各スレッドも独自のコピーを作成しますローカルツー。ただし、2 つの異なるコピーローカルツー最終的にはヒープ上の同じオブジェクトを指すことになります。事実は、ローカルツー静的変数によって参照されるオブジェクトを指します実例。静的変数のコピーは 1 つだけあり、そのコピーはヒープに保存されます。
したがって、両方のコピーローカルツー最終的には同じ共有インスタンスを指すことになります。共有インスタンスもヒープに保存されます。一致しますオブジェクト 3上の図では。
Sharedクラスには 2 つのメンバー変数も含まれることに注意してください。メンバー変数自体はオブジェクトとともにヒープに保存されます。2 つのメンバー変数が他の 2 つのオブジェクトを指します整数。これらの整数オブジェクトは以下に対応します。オブジェクト 2とオブジェクト 4図上では。
また、two()メソッドは、という名前のローカル変数を作成することに注意してください。ローカルワン。このローカル変数は、 Integer型のオブジェクトへの参照です。メソッドはリンクを設定しますローカルワン新しいIntegerインスタンスを指します。リンクはそのコピーに保存されますローカルワンスレッドごとに。2 つのIntegerインスタンスがヒープに格納され、メソッドは実行されるたびに新しいIntegerオブジェクトを作成するため、このメソッドを実行する 2 つのスレッドは別々のIntegerインスタンスを作成します。一致しますオブジェクト 1とオブジェクト 5上の図では。
プリミティブ型であるInteger型のSharedクラスの 2 つのメンバー変数にも注目してください。これらの変数はメンバー変数であるため、オブジェクトとともにヒープに保存されます。ローカル変数のみがスレッド スタックに保存されます。
GO TO FULL VERSION