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;

  1. = b;
  2. = 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;
  2. = 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或修改值)都是新生成一个对象,对形参的修改时,实参不受影响,与值传递的效果类似,但实际上仍是引用传递。

总结:

  1. 基本类型变量作为方法中的参数,进行的值传递,对形参的修改不影响实参的原来的值;
  2. 非final修饰的类、数组、接口作为方法中的参数,进行的引用传递(地址传递),对形参修改后实参也会改变,因为二者指向的是同一个实例;
  3. final修饰的类作为方法中的参数,因为final的存在初始化后值不可变,每次操作都相当于产生一个新的实例对象,因此对形参修改时,实参也不受影响。

以上是关于Jvm(31),理解升级----通过JVM内存模型深入理解值传递和引用传递两种方式的主要内容,如果未能解决你的问题,请参考以下文章

打怪升级jvm关于jvm内存模型及GC调优

JVM升级篇六(JVM内存模型)

jvm基础--JVM内存模型

jvm基础--JVM内存模型

深入理解JVM - 分代的基本概念

深入理解JVM—JVM内存模型