JVMメモリモデルとオブジェクト参照の分析例

目次

 

JVMのメモリモデルには、次の設定があります。

最初の栗 

2番目の例

3番目の例:

4番目の栗


インターネットでそのような分析を目にすることはめったにありません。場所によっては常に曖昧であることがわかります。実際、オブジェクトについて詳しく説明する栗はもっとあるはずです。例はいつですか。たとえば、クラスの読み込みプロセスはどこで作成されますか?たとえば、ヒープまたはスタック、いくつ作成されていますか?どんな関係?インターネット上には、基本的に特定の栗のない理論がありますが、この分野で優れた文学を持っている学生がいる場合は、メッセージを残してリンクを投稿してください、ありがとうございます

JVMのメモリモデルには、次の設定があります。

1.メインメモリとしてヒープメモリと呼ばれるメモリ空間があります。

2.スレッドには、コールスタックとも呼ばれるスレッドスタックと呼ばれる独自のローカルメモリがあります。

3.スレッドスタックには、現在のスレッドによって実行されたメソッド呼び出しに関する情報と、現在のメソッドのローカル変数情報が含まれています。

4.各スレッドは、独自のスレッドスタックにのみアクセスでき、他のスレッドのスレッドスタックにはアクセスできません。

5.プリミティブ型(boolean、byte、short、char、int、long、float、double)のすべてのローカル変数はスレッドスタックに直接格納され、各スレッドは独立していますが、プリミティブ型のコピーは間で転送できます。スレッド(それでも共有とは見なされません)。

6.非プリミティブ型のオブジェクトはヒープに格納され、このオブジェクトへの参照はスタックに格納されます。

7.オブジェクトのmemberメソッドの元のタイプがスタックに格納されます。

8.プリミティブ型とパッケージ型を含むオブジェクトメンバー変数、および静的型変数は、クラス自体と一緒にヒープに格納されます。

9.スレッドがオブジェクトの元の型メンバー変数を使用する場合、スレッドはそのコピーをスレッドスタックにコピーします。

10.スレッドがオブジェクトのラッパー型変数を使用する場合、スレッドはヒープに直接アクセスします。

 

上記の点に関して、以下では、いくつかの簡単な例を使用して説明します。合計4つの例を使用し、最初にすべてのコードを使用してから、個別に分析します。

package test;
 
import java.text.SimpleDateFormat;
import java.util.Date;
 
 
public class Test {
 
	public static void main(String[] args) {
		
		case1();
		case2();
		case3();
		case4();
		
	}
	
	public static void case1(){
		
		Test configA=new Test();
		configA.setId(10);
 
		Test configB=configA;
		System.out.println(configA.getId());
		configB.setId(20);
		System.out.println(configA.getId());
 
		System.out.println(configA.hashCode());
		System.out.println(configB.hashCode());
 
	}
	
	public static void case2(){
		
		Test config=new Test();
		config.setTestFieldClass(new TestFieldClass());
 
		TestFieldClass fieldClass=config.getTestFieldClass();
		System.out.println(config.getTestFieldClass().getId());
		fieldClass.setId(20);
		System.out.println(config.getTestFieldClass().getId());
 
		System.out.println(fieldClass.hashCode());
		System.out.println(config.getTestFieldClass().hashCode());
 
	}
	
	public static void case3(){
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
 
        	String a = "1970-01-01";
        	
            Test config=new Test();
            config.setDate(sdf.parse(a));
 
            Date blockTime = config.getDate();
 
            blockTime = sdf.parse("2018-06-28");
            System.out.println(sdf.format(blockTime));
            System.out.println(sdf.format(config.getDate()));
            
 
        } catch (Exception e) {
            e.printStackTrace();
        }
	}
	
	public static void case4(){
		
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
 
        	String a = "1970-01-01";
        	
            Test config=new Test();
            config.setDate(sdf.parse(a));
 
            Date blockTime = config.getDate();
 
            blockTime.setTime(new Date().getTime());
            System.out.println(sdf.format(blockTime));
            System.out.println(sdf.format(config.getDate()));
            
        } catch (Exception e) {
            e.printStackTrace();
        }
	}
	
	
	public Integer id;
	public Date date;
	public TestFieldClass testFieldClass;
	
	public Date getDate() {
		return date;
	}
 
	public void setDate(Date date) {
		this.date = date;
	}
 
	public Integer getId() {
		return id;
	}
 
	public void setId(Integer id) {
		this.id = id;
	}
 
	public TestFieldClass getTestFieldClass() {
		return testFieldClass;
	}
 
	public void setTestFieldClass(TestFieldClass testFieldClass) {
		this.testFieldClass = testFieldClass;
	}
 
	/**
	 * Test类的成员变量
	 */
	public static class TestFieldClass {
		public Integer id;
 
		public Integer getId() {
			return id;
		}
 
		public void setId(Integer id) {
			this.id = id;
		}
		
	}
}

運転結果

10
20
709769211
709769211
ヌル
20
1966953839
1966953839
2018年6月28日
1970-01-01
2018年6月28日
2018年6月28日 

最初の栗 

	public static void case1(){
		
		Test configA=new Test();
		configA.setId(10);
 
		Test configB=configA;
		System.out.println(configA.getId());
		configB.setId(20);
		System.out.println(configA.getId());
 
		System.out.println(configA.hashCode());
		System.out.println(configB.hashCode());
 
	}

10
20
776894132
776894132

分析:

1.メモリモデルの設定により、コード実行時

テストconfigA = new Test();
configA.setId(10);

実際、Testクラスのオブジェクトがヒープメモリに作成され、ヒープメモリに保存されてから、configAによってポイントされる場合、configAは、次のように、スレッドスタック内の単なる参照です。

2.コードが実行されたとき

Test configB=configA;

当時、configAのオブジェクト(configBという名前)への参照を作成しました。この参照はconfigAを指すのではなく、ヒープメモリ内のオブジェクト自体を直接指すため、次のようになります。

configAとconfigBの両方がこのオブジェクトへの参照であり、メモリのセクションを共有していることがわかります。

3.コードが実行されたとき

configB.setId(20);
の場合、configAとconfigBはオブジェクトを共有するため、configBはヒープメモリ内のオブジェクトのIDを20に設定します。したがって、後でconfigAオブジェクトのIDが出力されると、出力は20になります。次の図に示すように:

4. 2つのオブジェクトを共有するため、コードによって出力されるハッシュ値は同じです。

 

2番目の例

	public static void case2(){
		
		Test config=new Test();
		config.setTestFieldClass(new TestFieldClass());
 
		TestFieldClass fieldClass=config.getTestFieldClass();
		System.out.println(config.getTestFieldClass().getId());
		fieldClass.setId(20);
		System.out.println(config.getTestFieldClass().getId());
 
		System.out.println(fieldClass.hashCode());
		System.out.println(config.getTestFieldClass().hashCode());
 
	}

null
20
559102764
559102764

分析:

1.メンバー変数testFieldClassは、オブジェクトconfigに設定されます。この例では、オブジェクトconfigは実際にはヒープメモリに格納されます。configメンバー変数testFieldClassのオブジェクトもヒープメモリに格納され、configメンバー変数testFieldClassも格納されます。このオブジェクトを指します。以下に示すように、参照:

2.コードが実行されたとき

TestFieldClass fieldClass = config.getTestFieldClass();の場合、
作成されたfieldClassは実際にはこのオブジェクトへの参照であり、オブジェクト自体はヒープメモリに格納されます。この時点で、作成されたfieldClassは、のtestFieldClass属性と同じです。以下に示すように、configオブジェクト、および両方がこのオブジェクトの参照を指します

3.コードが実行されたとき

fieldClass.setId(20);

fieldClassは、このオブジェクトのIDを20に設定します。これにより、以下に示すように、ヒープメモリ内のこのオブジェクトのID値が実際に変更されます。

このため、後でconfig.getTestFieldClass()。getId()を出力すると、出力結果は20になります。

4.前述のように、fieldClassオブジェクトとconfigオブジェクトのtestFieldClass属性はこのオブジェクトへの参照であるため、最終的には同じハッシュ値559102764を出力します。

 

3番目の例:

public static void case3(){
		
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	try {
 
            String a = "1970-01-01";
        	
            Test config=new Test();
            config.setDate(sdf.parse(a));
 
            Date blockTime = config.getDate();
 
            blockTime = sdf.parse("2018-06-28");
            System.out.println(sdf.format(blockTime));
            System.out.println(sdf.format(config.getDate()));
            
 
        } catch (Exception e) {
            e.printStackTrace();
        }
}

出力結果に注意してください。2つの日付は異なります。

2018年6月28日
1970-01-01

分析:

1.変数blockTimeは、構成変数のdate属性から取得されます。最初はblockTimeの日付が最初の1970-01-01でしたが、blockTimeの時刻が2018-06-28に変更されました。出力結果から、変数blockTimeの時刻が変更されましたが、config変数のdate属性値はblockTimeの変更によって変更されません。

2.一見したところ、この例はJVMメモリモデルの設定に準拠していません。ほとんどの場合、基本型のみがスレッドスタックに格納され、Dateクラスは基本型ではないためです。ヒープメモリに格納されている、一種の参照共有。

3. 2つの日付の出力が異なる理由は、次のコード行にあります。

blockTime = sdf.parse( "2018-06-28");
等号を持つこのタイプのステートメントを代入ステートメントと呼ぶことがよくありますメモリモデルの観点からは、これは代入ではなく、参照のリダイレクトです。 、Date blockTime = config.getDate();ここでは、次の図に示すように、blockTime参照が指すターゲットとconfigのdateパラメーターが指すターゲットは同じです。

しかし、blockTime = sdf.parse( "2018-06-28");の場合、等号の右側の部分は、ヒープメモリに新しいDateオブジェクトを作成し、blockTimeが彼への参照を指すようにします。つまり、これからblockTimeとconfigのdate属性は問題なく、次のようになります。

ご覧のとおり、プロセス全体で、configのdate属性によって参照されるターゲットは変更されていません。そのため、上記の出力は異なります。

 

上記の3番目の例から、非基本変数が実際にヒープメモリに格納されており、参照のリダイレクト(等号)により、参照がヒープメモリ内の他のオブジェクトを直接指すようになることがわかります。

参照点が移動しました。前のオブジェクトはどうですか?JVMのガベージコレクター(GC)が待機しています。ヒープメモリ内のオブジェクトを誰も指していない場合(または、GCアルゴリズムによっては、一定期間誰も指していない場合)、GCはオブジェクトをドラッグします。離れて破壊し、次に彼が占有していたメモリを解放します。もちろん、例3の1970-01-01のDateオブジェクトはGCによってリサイクルされません。blockTimeのポイントは削除されますが、configのdate属性はそれを指します。

 

4番目の栗

public static void case4(){
		
	SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        try {
 
            String a = "1970-01-01";
        	
            Test config=new Test();
            config.setDate(sdf.parse(a));
 
            Date blockTime = config.getDate();
 
            blockTime.setTime(new Date().getTime());
            System.out.println(sdf.format(blockTime));
            System.out.println(sdf.format(config.getDate()));
            
        } catch (Exception e) {
            e.printStackTrace();
        }
}

出力結果:

2018-06-28
2018-06-28

分析:

1.この例のblockTimeは次のメソッドを使用して値を割り当てるため、この例の出力時間は同じです。

blockTime.setTime(new Date()。getTime());
例3の参照ターゲットを変更する代わりに、blockTimeオブジェクトのコンテンツを直接変更します。メモリモデルでのこの例の結果は次のとおりです。

上記は、JVMメモリモデルのいくつかの例です。メモリモデルをよりよく理解することは、開発作業に非常に役立ち、多くの落とし穴を掘り起こす可能性があります。

 

 

おすすめ

転載: blog.csdn.net/Goligory/article/details/104547880