Java SE 学习---内存管理&函数参数传递

来源:互联网 发布:知乎每日精选接口 编辑:程序博客网 时间:2024/06/16 19:42
  • Java的内存管理

Java虚拟机管理的存储空间在逻辑上可以分为两个区域:1)栈区域 2)堆区域。在这两个区域上存储的数据也不一样

  1. 栈区用来存储基本类型的变量以及对象的引用变量,当程序的逻辑超出了存储在这个区域的变量的作用域之后,Java虚拟机会自动释放掉这些变量所占用的存储空间
  2. 堆区用来存储由 new 创建的对象和数组由 Java 虚拟机的自动垃圾回收器来管理,在Java中在堆中产生了一个数组或者对象之后,在栈中定义一个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或者对象,引用变量就相当于是为数组或者对象起的一个名称。

具体情况下面举例说明:

public class Student {   String stuId;   String stuName;   int stuAge; } public class TestStudent {   public static void main(String[] args) {     Student zhouxingxing = new Student();     String name = new String("旺旺");      int a = 10;     char b = 'm';     zhouxingxing.stuId = "9527";     zhouxingxing.stuName = "周星星";     zhouxingxing.stuAge = 25;   } }
在上面的代码中
 Student zhouxingxing = new Student();
定义了一个指向Student的引用类型变量zhouxingxing并将它的值设为new Student()对象在堆中的首地址

String name = new String("旺旺");  
同样指向String的引用类型变量name的值设为对象 new String("旺旺")在堆中的首地址

int a = 10;  char b = 'm'; 
a和b为基本类型的变量他们的存储空间位于栈区,他们存储在为其分配的空间的内容就是他们的值。

下图是上述例子的内存分布图:

引用类型中的数组也封装了指针,即便是基本数据类型的数组也封装了指针,数组也是引用类型。比如代码int[] arr = new int[]{23,2,4,3,1};如下图所示:

  • Java的函数的参数传递

关于Java的函数的参数传递的问题,有人认为Java调用方法时向方法传递的是变量的值,有人认为传递的是引用(即指针)。其实我认为这两种说法都有理,引用类型的变量存的其实就是一个对象或者数组的地址(本质上也使用几个字节的空间存储的数值)这和基本类型的变量存储的值没有什么区别只是表示的含义不一样而已,因此我们可以认为Java的参数传递方式是“值”传递,说是引用传递也说得过去,因为在Java中自定义类型变量的使用频率远远超出了基本类型的变量,只不过基本类型的变量的值是一种特殊的“引用”它并不是指向堆中某一块存储区域的地址而就是其要表示的数值本身。

举一个例子:

class A{...}void method(A param){...}void method(int param){...}A a=new A();int test=12;method(a);method(test);
当调用method(A param)时,A类型的实参a将指向的对象的地址传递给形参,在方法method(A param)中对形参的修改(这里的修改指的是对其指向的堆空间的对象数据的修改)都会改变实参指向的空间的值。而当调用method(int param)时形参(基本类型的变量int test)将自己的值传递给形参,形参会在栈中拷贝一份实参的值,在方法内部对形参的修改将不会对实参的值造成任何的影响。

关于Java的参数传递有一个特殊的情况,就是传递String类型的变量时,形参的改变并不会对实参造成改变

例如:

public class TestString {   public static void main(String[] args) {          String name = "wangwang";     TestString testString = new TestString();          System.out.println("方法调用前:" + name);     testString.change(name);     System.out.println("方法调用后:" + name);   }      void change(String str) {     str = "旺旺老师";     System.out.println("方法体内修改值后:" + str);   } } 结果:方法调用前:wangwang 方法体内修改值后:旺旺老师 方法调用后:wangwang 

分析:我们看初始情况,即String name = "wangwang";这行代码运行
完,如下图:
JAVA内存管理 - 小白 - 小白的博客

 当调用方法时testString.change(name),内存变化为:

JAVA内存管理 - 小白 - 小白的博客


在方法体内,参数str赋予一个新值,str = "旺旺老师"。由于String是final类型的对象一经创建之后就不允许对其进行修改,当然就不会允许将str(地址为36DF)的空间的值设置为“旺旺老师”,在这种情况下系统就会在堆中分配一块新的内存空间37DF并将37DF这块内存区域的值设置为“旺旺老师”,并将str指向这块内存区域,而name还是指向36DF, 37DF的改变对它已没影响:

JAVA内存管理 - 小白 - 小白的博客

最后,方法调用结束,str与37DF的内存空间消亡。Name的值依然为wangwang,并没有改变。
所以String虽然是引用类型参数,但值依然不变:


关于String类型更多的分析见这篇blog

最后是一个综合的例子:

public class TestChange {   void change(Student stu1, Student stu2) {     stu1.stuAge ++;     stu2.stuAge ++;     Student stu = stu1;     stu1 = stu2;     stu2 = stu;   }      public static void main(String[] args) {          Student furong = new Student();     furong.stuName = "芙蓉姐姐";     furong.stuAge = 30;          Student fengjie = new Student();     fengjie.stuName = "凤姐";     fengjie.stuAge = 26;          TestChange testChange = new TestChange();     testChange.change(furong, fengjie);          System.out.println(furong.stuName);     System.out.println(furong.stuAge);          System.out.println(fengjie.stuName);     System.out.println(fengjie.stuAge);   }  } 运行结果:芙蓉姐姐 31 凤姐 27 
分析:

0 0
原创粉丝点击