从内存层面分析Java 参数传递机制

在 Java 中,理解参数传递机制对于编写高效和可维护的代码至关重要。本文将探讨基本数据类型和引用数据类型的参数传递方式,并介绍 System.identityHashCode 方法及其作用。我们将结合栈帧的概念,通过示例代码来详细解释这些机制。

System.identityHashCode 的作用

System.identityHashCode 方法用于获取对象的哈希码,这个哈希码是基于对象的内存地址的。它可以帮助我们判断两个变量是否指向同一个对象。这个哈希码与 Object 类中的 hashCode 方法不同,后者可能被重写,而 identityHashCode 保证基于对象的实际内存地址。

栈帧的概念与参数传递

⚠️:先普及一下基本知识:一个线程对应一个栈,一个栈包含多个栈帧,一个栈帧对应一个方法。

每当一个方法被调用时,Java 虚拟机(JVM)会为该方法创建一个新的栈帧。栈帧用于存储方法的局部变量、操作数栈和其他执行上下文信息。局部变量表包含了方法中的所有参数和局部变量。每个方法调用都有自己的栈帧,这些栈帧在方法调用结束后会被弹出。

基本数据类型的参数传递

对于基本数据类型(如 int, float, boolean 等),Java 使用 值传递 的方式。这意味着方法接收的是参数值的副本,而不是原始变量的引用。修改副本不会影响原始变量。

示例代码
public class Main {
    public static void main(String[] args) {
        int a = 1;
        int hashCode = System.identityHashCode(a);
        System.out.println("a: " + hashCode); // a:1456208737
        modify(a);
        System.out.println("a: " + hashCode); // a:1456208737
    }

    public static void modify(int b) {
        b = 2;
        int hashCode = System.identityHashCode(b); 
        System.out.println("b: " + hashCode); // b:288665596
    }
}
解释
  1. 方法调用过程

    • main 方法中的栈帧包含了局部变量 a,其值为 1,哈希码为 1456208737
    • 当调用 modify 方法时,a 的值 1 被复制到 modify 方法的栈帧中的局部变量 b
  2. modify 方法中的操作

    • modify 方法的栈帧中,b 的值为 1。将 b 的值修改为 2,并将其哈希码输出为 288665596
    • 由于 bmain 方法栈帧中 a 值的副本,修改 b 不会影响 a 的值。
  3. 栈帧作用

    • main 方法和 modify 方法各自拥有独立的栈帧,局部变量表中的 ab 是不同的变量,修改 b 不会影响 a

引用数据类型的参数传递

对于引用数据类型(如 Person 对象),Java 使用也是值传递(只不过传递的值是引用数据类型的内存地址)的方式。这意味着方法接收的是对象引用的副本,而不是对象的实际值。修改对象内容会影响所有引用该对象的变量,但修改引用本身不会影响原始引用。

示例代码
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("mike");
        int hashCode = System.identityHashCode(p1); 
        modify(p1);
        System.out.println("p1: " + hashCode); // p1:1456208737
    }

    public static void modify(Person p2) {
        p2.name = "lucy";
        int hashCode = System.identityHashCode(p2); 
        System.out.println("p2: " + hashCode); // p2:1456208737
    }

    static class Person {
        String name;

        public Person(String name) {
            this.name = name;
        }
    }
}
解释
  1. 方法调用过程

    • main 方法中的栈帧包含了局部变量 p1,它是指向 Person 对象的引用,其哈希码为 1456208737
    • 当调用 modify 方法时,p1 的引用被复制到 modify 方法的栈帧中的局部变量 p2
  2. modify 方法中的操作

    • modify 方法的栈帧中,此时p2 也是指向同一个 Person 对象的引用。修改 p2.name 会直接影响到 p1 对象,因为它们指向同一个内存地址。
    • p2 的哈希码与 p1 相同,因为它们是同一个对象的引用。
  3. 栈帧作用

    • main 方法和 modify 方法各自拥有独立的栈帧,但 p1p2 指向同一个内存地址,即同一个 Person 对象,修改 p2 的内容会影响 p1

String 类型的特殊情况

String 类型是一个引用数据类型,但它是 不可变的。这意味着每次对 String 对象进行修改时,实际上会创建一个新的 String 对象,而不会改变原始对象。

示例代码
public class Main {
    public static void main(String[] args) {
        String a = "str1";
        int hashCode = System.identityHashCode(a); 
        modify(a);
        System.out.println("a: " + hashCode); // a:1456208737
    }

    public static void modify(String b) {
        b = "str2";
        int hashCode = System.identityHashCode(b); 
        System.out.println("b: " + hashCode); // b:288665596
    }
}
解释
  1. 方法调用过程

    • main 方法中的栈帧包含了局部变量 a,它指向 String 对象 "str1",其哈希码为 1456208737
    • 当调用 modify 方法时,a 的引用(即 "str1" 的地址)被复制到 modify 方法的栈帧中的局部变量 b
  2. modify 方法中的操作

    • modify 方法中,将 b 赋值为 "str2"。按理来说操作的也是同一个内存地址,也会将该内存地址中的值修改为 "str2"。但是Java规定了 String 是不可变的,这将会在常量池创建一个新的 String 对象 "str2",并将 b 指向这个新的对象。原始的 String 对象 "str1" 仍然存在,因此a 的哈希码没有改变。
  3. 栈帧作用

    • main 方法和 modify 方法各自拥有独立的栈帧。修改 b 的引用不会影响 a,因为 String 对象的不可变性确保了原始对象不变。

总结

  • 基本数据类型 的参数传递是值传递,修改方法内部的值不会影响外部变量,因为每个方法调用都有自己的栈帧。
  • 引用数据类型 的参数传递是引用传递,修改方法内部的对象内容会影响外部变量,因为它们指向同一个对象。栈帧中的引用指向相同的对象内存地址。
  • String 类型 是不可变的,对 String 对象的修改会创建一个新的对象,原始对象保持不变,这与对象的可变性相关。

你可能感兴趣的:(java,开发语言)