Jvm(31),理解升级----通过JVM内存模型深入理解值传递和引用传递两种方式
Posted qingruihappy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Jvm(31),理解升级----通过JVM内存模型深入理解值传递和引用传递两种方式相关的知识,希望对你有一定的参考价值。
值传递和引用传递分析
Java中数据类型分为两大类:基本类型和引用类型(也就是对象类型)。
基本类型:boolean、char、byte、short、int、long、float、double
引用类型:类、接口、数组
因此,变量类型也可分为两大类:基本类型和引用类型。
在分析值传递和引用传递之前,建议了解下以上变量类型在Java内存管理模型中的位置,如果对此有所了解,将更加有助于理解两种传递的方式^_^
在Java内存中,基本类型变量存储在Java栈(VM Stack)中,引用变量存储在堆(Heap)中,模型如下:
值传递和引用传递的定义:
这里要用实际参数和形式参数的概念来帮助理解
值传递:方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个 copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。
引用传递:
也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象。 demo1:
值传递:
public static void main(String[] args) {
System.out.println(" 值传递测试 ");
int a = 10;
int b = 20;
System.out.println("before swap " + "a = " + a + " b = " + b);
swap(a, b);
System.out.println("after swap " + "a = " + a + " b = " + b);
}
public static void swap(int a, int b) {
int temp = a;
- = b;
- = temp;
System.out.println("swaping " + "a = " + a + " b = " + b);
}
before swap a = 10 b = 20 swaping a = 20 b = 10 after swap a = 10 b = 20
从jvm的角度来理解的话,因为main也是一个方法,所以这个时候基本数据类型也是在栈中的,
假如是全局的话就会在堆中。要想理解基本数据类型的值传递我们首先来回顾一下jvm加载数据的过程,
从上面的例子我们看到,首先jvm会把class的相关信息放到方法区中,当代码执行到int a=10和int b=20的时候会把这些信息放到栈中去也就是局部变量表中去。这个时候在栈中的基本数据类型它本身就是真身,现在假如把它传到方法中进行对值进行操作的话,jvm规定就会复制一份数据出来,这个时候操作的就是数据复制的信息。而假如是引用数据类型的话,我们来看下面的案例
public class ParamTest {
public static void main(String[] args) {
Student a = new Student(0);
Student b = new Student(100);
System.out.println("交换前:");
System.out.println("a的分数:" + a.getScore() + "--- b的分数:" + b.getScore());
swap(a, b);
System.out.println("交换后:");
System.out.println("a的分数:" + a.getScore() + "--- b的分数:" + b.getScore());
}
public static void swap(Student x, Student y) {
x.setScore(99);
y.setScore(99);
}
}
交换前:
a的分数:0.0--- b的分数:100.0 交换后:
a的分数:99.0--- b的分数:99.0
我们再来看看引用数据类型的jvm的加载过程,首先当jvm类加载器加载class文件的时候会把所有的信息都放到方法区中,这个时候当加载到Student a = new Student(0); Student b = new Student(100);的时候会把a,b如栈,这个时候a.b只是引用的而真正的两个 student都在堆中,a和b只不过是化身而已,这个时候我们假如对化身a和b进行操作的话,它a和b本身就是对堆中的引用地址的代表,a和b这个时候就代表student在堆中的内存地址,所以这个时候假如对a和b进行操作的话就是对堆中的对象进行操作,所以会引起值的变化。
理解了上面的值传递和引用传递我们再来看看下面的案例
public static void main(String[] args) {
System.out.println(" 值传递测试 ");
int a = 10;
int b = 20;
System.out.println("before swap " + "a = " + a + " b = " + b);
swap(a, b);
System.out.println("after swap " + "a = " + a + " b = " + b);
}
public static void swap(int a, int b) {
- = 1;
- = 1;
System.out.println("swaping " + "a = " + a + " b = " + b);
}
before swap a = 10 b = 20 swaping a = 1 b = 1
after swap a = 10 b = 20
道理就是值传递,我们主要是为了引出下面的例子 public static void main(String[] args) {
Student a = new Student(0);
Student b = new Student(100);
System.out.println("交换前:");
System.out.println("a的分数:" + a.getScore() + "--- b的分数:" + b.getScore());
swap(a, b);
System.out.println("交换后:");
System.out.println("a的分数:" + a.getScore() + "--- b的分数:" + b.getScore());
}
public static void swap(Student x, Student y) {
x=new Student(99);
y=new Student(99);
}
交换前:
a的分数:0.0--- b的分数:100.0
交换后:
a的分数:0.0--- b的分数:100.0
从上面我们可以看到本来a和b都分别指向堆中的new student(0)和student(10)的现在在另一个方法中现在又有x和y指向了堆中的new student(99)和new student(99)所以这个时候a和b的引用地址还是不变的,只不过是xy的变化了而已。本质是a和b分别指向了两个不同的堆内存空间
我们在来看final的例子
public static void main(String[] args) {
// String、Char、Byte、Short、Integer、Long、Float、Double等final修饰的类
// 对形参修改时实参不受影响
System.out.println(" final修饰的类-特殊的引用传递测试 ");
String str = "我是final我不变"; swapByFinalClass(str);
System.out.println("after swapByFinalClass, str = " + str);
}
/**
* final修饰的类做形参时, 修改形参不影响实参
*/
public static void swapByFinalClass(String str) {
str = "我是形参";
System.out.println("swapByFinalClassing : str = " + str);
}
final修饰的类-特殊的引用传递测试
swapByFinalClassing : str = 我是形参 after swapByFinalClass, str = 我是final我不变
只不过这个时候数据放在了方法区的常量池中了,而不是在堆中了。
1)使用基本类型的变量a、b通过swap方法进行的是值传递,对形参修改但实参未改变,
利用内存模型详解原理:
2)使用类ReferenceObj的实例变量obj,通过swapByReference()进行的是引用传递的方式,具体的内存模型如下:
通过上面的分析,对于传递方式应该很好理解了^_^
注意:这里要特殊考虑String,以及Integer、Double等基本类型包装类,它们的类前面都有final修饰,为不可变的类对象,每次操作(new或修改值)都是新生成一个对象,对形参的修改时,实参不受影响,与值传递的效果类似,但实际上仍是引用传递。
总结:
- 基本类型变量作为方法中的参数,进行的值传递,对形参的修改不影响实参的原来的值;
- 非final修饰的类、数组、接口作为方法中的参数,进行的引用传递(地址传递),对形参修改后实参也会改变,因为二者指向的是同一个实例;
- final修饰的类作为方法中的参数,因为final的存在初始化后值不可变,每次操作都相当于产生一个新的实例对象,因此对形参修改时,实参也不受影响。
以上是关于Jvm(31),理解升级----通过JVM内存模型深入理解值传递和引用传递两种方式的主要内容,如果未能解决你的问题,请参考以下文章