私はJavaでのトレーニングを受けており、Java Memory Modelの記事を勉強する必要がありました。理解を深めるために翻訳しましたが、善が失われないように、コミュニティと共有することにしました。初心者には便利だと思いますので、気に入っていただければ翻訳していきます。
元のJavaメモリモデルはあまり良くなかったため、Java 1.5で改訂されました。このバージョンは現在も使用されています(Java 14以降)。
内部Javaメモリモデル
JVMが内部的に使用するJavaメモリモデルは、メモリをスレッドスタックとヒープに分割します。この図は、論理的な観点からのJavaメモリモデルを示しています
。Java仮想マシンで実行されている各スレッドには、独自のスタックがあります。スタックには、現在の実行ポイントに到達するためにスレッドが呼び出したメソッドに関する情報が含まれています。これを「コールスタック」と呼びます。スレッドがそのコードを実行するとすぐに、呼び出しスタックが変更されます。
スレッドスタックには、実行される各メソッド(呼び出しスタック内のすべてのメソッド)のすべてのローカル変数が含まれます。スレッドは自分のスタックにしかアクセスできません。ローカル変数は、それらを作成したスレッドを除く他のすべてのスレッドからは見えません。 2つのスレッドが同じコードを実行していても、独自のスタックにそのコードのローカル変数を作成します。したがって、各スレッドには、各ローカル変数の独自のバージョンがあります。
プリミティブ型のローカル変数(boolean、byte、short、char、int、long、float、double)はすべてスレッドスタックに完全に格納され、他のスレッドからは見えません。あるスレッドは、プリミティブ変数のコピーを別のスレッドに渡すことができますが、プリミティブローカル変数を共有することはできません。
ヒープには、オブジェクトを作成したスレッドに関係なく、Javaアプリケーションで作成されたすべてのオブジェクトが含まれています。これには、プリミティブ型のオブジェクトのバージョン(Byte、Integer、Longなど)が含まれます。オブジェクトが作成されてローカル変数に割り当てられたのか、別のオブジェクトのメンバー変数として作成されたのかは関係なく、ヒープに格納されます。
スレッドスタックに格納されているコールスタックとローカル変数、およびヒープに格納されているオブジェクトを示す図を次に示します。
ローカル変数はプリミティブ型にすることができます。その場合、スレッドのスタックに完全に格納されます。
ローカル変数をオブジェクト参照にすることもできます。この場合、参照(ローカル変数)はスレッドスタックに格納されますが、オブジェクト自体はヒープに格納されます。
オブジェクトにはメソッドを含めることができ、これらのメソッドにはローカル変数を含めることができます。これらのローカル変数は、メソッドを所有するオブジェクトがヒープに格納されている場合でも、スレッドスタックに格納されます。
オブジェクトのメンバー変数は、オブジェクト自体とともにヒープに格納されます。これは、メンバー変数がプリミティブ型である場合と、オブジェクト参照である場合の両方に当てはまります。
静的クラスの変数も、クラス定義とともにヒープに格納されます。
ヒープ上のオブジェクトは、オブジェクトへの参照を持つすべてのスレッドからアクセスできます。スレッドがオブジェクトにアクセスできる場合、スレッドはそのオブジェクトのメンバー変数にもアクセスできます。 2つのスレッドが同じオブジェクトのメソッドを同時に呼び出す場合、どちらのスレッドもオブジェクトのメンバー変数にアクセスできますが、各スレッドはローカル変数の独自のコピーを持っています。
上記のポイントを示す図は次のとおりです
。2つのスレッドにローカル変数のセットがあります。ローカル変数2は、ヒープ上の共有オブジェクト(オブジェクト3)を指します。つまり、各スレッドには、独自の参照を持つローカル変数の独自のコピーがあります。したがって、2つの異なる参照がヒープ上の同じオブジェクトを指します。
一般的なオブジェクト3は、メンバー変数(矢印で示す)としてオブジェクト2およびオブジェクト4への参照を持っていることに注意してください。これらのリンクを介して、2つのスレッドがオブジェクト2とオブジェクト4にアクセスできます。
この図には、ローカル変数(ローカル変数1)も示されています。各コピーには、2つの異なるオブジェクト(オブジェクト1とオブジェクト5)を指す異なる参照が含まれ、同じオブジェクトは参照されません。理論的には、両方のスレッドがオブジェクト1とオブジェクト5の両方への参照を持っている場合、これらのスレッドは両方にアクセスできます。ただし、上の図では、各スレッドは2つのオブジェクトの1つのみを参照しています。
では、これらのイラストの結果、どのようなJavaコードになるのでしょうか。さて、以下のコードと同じくらい単純なコード:
Public class MyRunnable implements Runnable() {
public void run() {
methodOne();
}
public void methodOne() {
int localVariable1 = 45;
MySharedObject localVariable2 =
MySharedObject.sharedInstance;
//... do more with local variables.
methodTwo();
}
public void methodTwo() {
Integer localVariable1 = new Integer(99);
//... do more with local variable.
}
}
public class MySharedObject {
// , MySharedObject
public static final MySharedObject sharedInstance =
new MySharedObject();
// -,
public Integer object2 = new Integer(22);
public Integer object4 = new Integer(44);
public long member1 = 12345;
public long member2 = 67890;
}
run()メソッドはmethodOne()を呼び出し、methodOne()はmethodTwo()を呼び出します。
methodOne()は、int型のプリミティブローカル変数(localVariable1)およびオブジェクト参照であるローカル変数(localVariable2)を宣言します。
One()メソッドを実行する各スレッドは、それぞれのスタック上にlocalVariable1およびlocalVariable2の独自のコピーを作成します。 localVariable1変数は互いに完全に分離され、各スレッドのスタックに配置されます。あるスレッドは、別のスレッドがlocalVariable1のコピーにどのような変更を加えているかを確認できません。
One()メソッドを実行する各スレッドは、localVariable2の独自のコピーも作成します。ただし、localVariable2の2つの異なるコピーは、ヒープ上の同じオブジェクトをポイントします。ポイントは、localVariable2がsharedInstance静的変数によって参照されるオブジェクトを指しているということです。静的変数のコピーは1つだけあり、そのコピーはヒープに格納されます。したがって、localVariable2の両方のコピーは、同じMySharedObjectインスタンスをポイントすることになります。MySharedObjectインスタンスもヒープに格納されます。上の図のオブジェクト3に対応します。
MySharedObjectクラスには2つのメンバー変数も含まれていることに注意してください。メンバー変数自体は、オブジェクトとともにヒープに格納されます。2つのメンバー変数は、他の2つのIntegerオブジェクトを指しています。これらの整数オブジェクトは、図のオブジェクト2とオブジェクト4に対応しています。
また、methodTwo()はlocalVariable1という名前のローカル変数を作成することに注意してください。このローカル変数は、Integerオブジェクトへの参照です。このメソッドは、参照localVariable1を新しいIntegerインスタンスを指すように設定します。リンクは、各スレッドのlocalVariable1の独自のコピーに保存されます。 2つのIntegerインスタンスはヒープに格納され、メソッドは実行されるたびに新しいIntegerオブジェクトを作成するため、このメソッドを実行する2つのスレッドは別々のIntegerインスタンスを作成します。これらは、上の図のオブジェクト1とオブジェクト5に対応しています。
また、プリミティブ型であるlong型のMySharedObjectクラスの2つのメンバー変数にも注意してください。これらの変数はメンバー変数であるため、オブジェクトとともにヒープに保存されます。ローカル変数のみがスレッドスタックに格納されます。
パート2はこちらです。