Javaの変数保存メカニズムとパラメータ渡しの仕組み

JVMメモリメカニズム

プリミティブ型の保存と値渡し

プリミティブ型の保存

  • プリミティブ型のローカル変数はJVMスタックに保存されます。まずJVMはAという名前の変数を作成し、ローカル変数テーブルに存在します。その後、スタック内にリテラル値10の内容が保存されているか確認します。存在する場合は、Aをそのアドレスに直接指します。存在しない場合は、JVMはスタック内に10という内容を保存するためのスペースを確保します。
  • int A = 10 を記述した場合:
  • int A = 10, int B = 10 を記述した場合:
  • int A = 10, int B = 10, B = 20; の場合

プリミティブ型の値渡し

  • 下図のように、mainメソッドを実行すると、JVMはスタックトップに新しいスタックフレームを追加します。このフレームにはA=10の変数が含まれます。passPrimitiveメソッドを実行すると、JVMはさらに新しいスタックフレームを追加します。このフレームにはB=10の変数が含まれます。B=100を実行すると、passPrimitiveフレーム内のB値が変更されますが、mainフレーム内のA値は変更されません。
  • Before: A is 10
  • B is 100
  • After: A is 10

参照型の保存と値渡し

参照型の保存

  • 参照型には基本型以外のすべての型が含まれます。変数にはヒープを指す参照が保存されます(スタックにヒープを指す参照が保存されます)。具体的なプロセスは以下の通りです。

参照型の渡し

  • 実引数が変更されます
  • Before: S1'ID is 100
  • In The method: S1'ID is 200
  • After: S1'ID is 200
  • 第21行(Student s1 = new Student())を実行したとき
  • 第24行(Testメソッドに入ったとき)

まとめ:

  • Javaには参照渡しはなく、値渡ししかありません。Javaにおける「値」の概念は2種類に分かれます。一つは基本型の実際の数値、もう一つは参照です。しかし、どちらの変数についても、Javaはアドレスを取得できません(参照変数は参照を保存していますが、変数自体のアドレスは取得できません)。つまり、Javaではオブジェクトの参照自体に対して操作はできません(例えば、別の参照に置き換えるなど)。そのため、参照渡しは存在しません。逆に、C++/Cでは、いかなる変数(基本変数、クラス、ポインタ変数など)についても、変数内の値を取得できるだけでなく、変数のアドレスも取得できるため、値渡し、参照渡し、ポインタ渡しの区別があります。

ラッパークラスのパラメータ渡しに関する問題: Integerを例として

  • すべてのラッパークラスは参照渡しですが、関数内でラッパークラスを渡しても実引数は変更されません。
  • すべてのラッパークラスは不変クラスであり、finalで修飾されています。例えばfinal int valueです。そのため、オブジェクトが再代入されると、新しいオブジェクトが生成されます。
  • Integerのキャッシュメカニズムに基づき、-128から127の範囲内であれば、定数プールからIntegerオブジェクトを取得して返します。範囲外の場合は、new Integer(value)を返します。
public class ValuePassing {
	
	public static void fun1(Integer i) {
		System.out.println("fun1内での代入前のiのアドレス: " + System.identityHashCode(i));
		i = 10; //定数プールからIntegerオブジェクトを取得するか、new Integer(value)を返す
		//代入後、アドレスが変更される
		System.out.println("fun1内での代入後のiのアドレス: " + System.identityHashCode(i));
	}
	
	public static void main(String[] args) {
        Integer i = 5;
        System.out.println("main内でのiのアドレス: " + System.identityHashCode(i));
        fun1(i);
        
        i = 20;
        //代入後、アドレスが変更される
        System.out.println("main内での再代入後のiのアドレス: " + System.identityHashCode(i));
    }
}

出力:
main内でのiのアドレス: 1072591677
fun1内での代入前のiのアドレス: 1072591677
fun1内での代入後のiのアドレス: 1523554304
main内での再代入後のiのアドレス: 1175962212

キャッシュについて:

public class ValuePassing {
	
	public static void fun1(Integer i, Integer j) {
		System.out.println("fun1内での代入前のiのアドレス: " + System.identityHashCode(i));
		System.out.println("fun1内での代入後のjのアドレス: " + System.identityHashCode(j));
		//iとjに同じ値を代入後(-128-127の範囲内)、アドレスが同じになる
		i = 10; 
		j = 10;
		System.out.println("-----------------------------------------------");
		System.out.println("fun1内での代入後のiのアドレス: " + System.identityHashCode(i));
		System.out.println("fun1内での代入後のjのアドレス: " + System.identityHashCode(j));
	}
	
	public static void main(String[] args) {
        Integer i = 5;
        Integer j = 20;
        System.out.println("main内でのiのアドレス: " + System.identityHashCode(i));
        System.out.println("main内でのjのアドレス: " + System.identityHashCode(j));
        System.out.println("-----------------------------------------------");
        fun1(i, j);
    }
}

出力:
main内でのiのアドレス: 1072591677
main内でのjのアドレス: 1523554304
-----------------------------------------------
fun1内での代入前のiのアドレス: 1072591677
fun1内での代入後のjのアドレス: 1523554304
-----------------------------------------------
fun1内での代入後のiのアドレス: 1175962212
fun1内での代入後のjのアドレス: 1175962212
public class ValuePassing {
	
	public static void fun1(Integer i, Integer j) {
		System.out.println("fun1内での代入前のiのアドレス: " + System.identityHashCode(i));
		System.out.println("fun1内での代入後のjのアドレス: " + System.identityHashCode(j));
		
		//-128-127の範囲外ではアドレスが異なる
		i = 1000; 
		j = 1000;
		System.out.println("-----------------------------------------------");
		System.out.println("fun1内での代入後のiのアドレス: " + System.identityHashCode(i));
		System.out.println("fun1内での代入後のjのアドレス: " + System.identityHashCode(j));
	}
	
	public static void main(String[] args) {
        Integer i = 5;
        Integer j = 20;
        System.out.println("main内でのiのアドレス: " + System.identityHashCode(i));
        System.out.println("main内でのjのアドレス: " + System.identityHashCode(j));
        System.out.println("-----------------------------------------------");
        fun1(i, j);
    }
}

出力:
main内でのiのアドレス: 1072591677
main内でのjのアドレス: 1523554304
-----------------------------------------------
fun1内での代入前のiのアドレス: 1072591677
fun1内での代入後のjのアドレス: 1523554304
-----------------------------------------------
fun1内での代入後のiのアドレス: 1175962212
fun1内での代入後のjのアドレス: 918221580

タグ: JVM Java メモリ管理 値渡し 参照渡し

5月31日 05:48 投稿