从JVM的内存结构出发,探究Java的值传递

splend21 Lv1
1.Java中只有值传递

相较于其他程序设计语言(比如 C++、 Pascal)提供的两种参数传递的方式,在 Java 中只有值传递。

  • 值传递:方法接收的是实参值的拷贝,会创建副本。
  • 引用传递:方法接收的直接是实参的地址,而不是实参内的值,这就是指针,此时形参就是实参,对形参的任何修改都会反应到实参,包括重新赋值。
2.一段Java程序是如何运行的

在讨论Java的值传递之前,先来看看一段Java程序运行时,JVM的内存变化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MethodExampleTest {

public static void main(String[] args) {
method1(10);
}

private static void method1(int x) {
int y = x + 1;
Object m = method2();
System.out.println(m);
}

private static Object method2() {
Object n = new Object();
return n;
}
}

首先当我们运行这个类的时候,首先做的事就是类加载,将该类的字节码文件加载到JVM虚拟机中,对应其中的方法区内存(为了观感,这里使用源代码的形式)

加载完成之后首先启动对应main的主线程,同时分配一块栈内存,之后由任务调度器调度执行,当CPU核心的时间片分给了我们的主线程,main方法作为入口首先被分配一块栈帧,配合每个线程独有的程序计数器(记录当前线程正在执行的字节码指令地址),开始运行主线程中的代码(栈帧中的局部变量表对应方法中的局部变量和方法参数)

方法运行并发现main方法需要调用method1,同上创建出一块栈帧,根据程序计数器执行代码并进行赋值,直到执行到调用method2

再次创建出method2栈帧,发现需要创建对象于是在堆中创建一个Object对象并赋值,直到执行到return语句

直到方法依次调用结束,释放无用栈帧,并根据记录的返回地址,继续执行接下来的代码直到所有栈帧均被释放,程序运行结束。

经过上面的内容在来看Java的值传递,相信下面的几个例子已经难不倒你了。

3.值传递的几个例子
1.传递基本类型参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {
int num1 = 10;
int num2 = 20;
swap(num1, num2);
System.out.println("num1 = " + num1);
System.out.println("num2 = " + num2);
}

public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
System.out.println("a = " + a);
System.out.println("b = " + b);
}

//输出:
//a = 20
//b = 10
//num1 = 10
//num2 = 20

swap() 方法中,ab 的值进行交换,并不会影响到 num1num2。因为交换操作发生在自己的方法栈帧中。作为副本的内容无论怎么修改,都不会影响到原件本身。

2.传递引用参数类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  public static void main(String[] args) {
int[] arr = { 1, 2, 3, 4, 5 };
System.out.println(arr[0]);
change(arr);
System.out.println(arr[0]);
}

public static void change(int[] array) {
// 将数组的第一个元素变为0
array[0] = 0;
}

//输出:
//1
//0

这里看着像引用传递,但其实传递的还是值,这个值就是数组在堆内存中的地址,由 arr 与 array 共同指向,所以方法内部的修改才能影响到实参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class Person {
private String name;
// 省略构造函数、Getter&Setter方法
}

public static void main(String[] args) {
Person xiaoZhang = new Person("小张");
Person xiaoLi = new Person("小李");
swap(xiaoZhang, xiaoLi);
System.out.println("xiaoZhang:" + xiaoZhang.getName());
System.out.println("xiaoLi:" + xiaoLi.getName());
}

public static void swap(Person person1, Person person2) {
Person temp = person1;
person1 = person2;
person2 = temp;
System.out.println("person1:" + person1.getName());
System.out.println("person2:" + person2.getName());
}

//输出:
//person1:小李
//person2:小张
//xiaoZhang:小张
//xiaoLi:小李

这里与上面的那种情况又有所不同,在进行方法调用的时候,由于是值传递,person1与person2作为xiaoZhang与xiaoLi的拷贝,各自存在不同的栈帧,正因为如此,方法栈帧内部的交换不会影响到其他栈帧,所以才没有影响到原来的 xiaoZhang 与 xiaoLi

Comments
On this page
从JVM的内存结构出发,探究Java的值传递