值传递和引用传递
一、堆(heap)和栈(statck)
1、栈(stack)
每个线程运行时所分配的内存为虚拟机栈,jvm为每个线程分配一个虚拟机栈,每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
每个栈有多个栈帧(Frame)组成,对应着每个方法调用时的所占用的内存(每个运行的方法对应一个栈帧)
每个线程只能有一个活动栈帧(正在执行的那个方法)、对应着当前正在执行的那个方法
栈是一种先进后出的数据结构,每一次方法的调用都对应一次方法的压栈,方法执行完毕后出栈 。有时候方法的递归调用就会出现栈内存溢出(StackOverflowError)
栈中主要存放一些基本数据类型的变量(byte,short,int,long,float,double,boolean,char)和堆中对象引用。
栈的优势是,存取速度比堆快,且数据存储按照一定顺序排列的。每个线程分配一个stack,即stack是线程独占的,即数据是独占的。但缺点是,存放在栈中的数据占用多少内存空间需要在编译时确定下来,缺乏灵活性。
2、堆(heap)
存储的全部是对象实例,每个对象都包含一个与之对应的class的信息(class信息存放在方法区)。
jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身,几乎所有的对象实例和数组都在堆中分配。
Java的堆是一个运行时数据区,类的对象从堆中分配空间。这些对象通过new等指令建立,通过垃圾回收器来销毁。
堆的优势是可以动态地分配内存空间,需要多少内存空间不必事先告诉编译器,因为它是在运行时动态分配的。但缺点是,由于需要在运行时动态分配内存,所以存取速度较慢。
5、字符串常量池存储在堆还是方法区?
JDK1.7 及之后版本的 JVM 已经将字符串常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一块区域存放运行时常量池。
JDK1.8开始,方法区的实现由永久代变成了元空间。
字符串常量池存在堆中:
这也就是有道面试题:String s = new String(“abc”);产生几个对象?答:一个或两个,如果常量池中原来没有”abc”,就是两个。
二、值传递和引用传递
1、什么是值传递?
当一个参数按照值的方式在两个方法之间传递时,调用者和被调用者其实是用的两个不同的变量——被调用者中的变量(原始值)是调用者中变量的一份拷贝,对它们当中的任何一个变量修改都不会影响到另外一个变量。
2、什么是引用传递?
而当一个参数按照引用传递的方式在两个方法之间传递时,调用者和被调用者其实用的是同一个变量,当该变量被修改时,双方都是可见的。
我们都知道数据基本数据类型有八种byte,char,short,int,long,float,double,boolean
基本类型 | 字节数 | 位数 | 最大值 | 最小值 |
---|---|---|---|---|
byte | 1byte | 8bit | 2^7-1 | -2^7 |
short | 2byte | 16bit | 2^15-1 | -2^15 |
int | 4byte | 32bit | 2^31-1 | -2^31 |
long | 8byte | 64bit | 2^63-1 | -2^63 |
float | 4byte | 32bit | 3.4028235E38 | 1.4E - 45 |
double | 8byte | 64bit | 1.7976931348623157E308 | 4.9E - 324 |
char | 2byte | 16bit | 2^16 - 1 | 0 |
其中还有一种常用类型 String
,该类型为引用类型
基本类型的变量存储的都是实际的值,而引用类型的变量存储的是对象的引用——指向了对象在内存中的地址。值和引用存储在 stack(栈)中,而对象存储在 heap(堆)中。
之所以有这个区别,是因为:
- **栈的优势是,存取速度比堆要快,**仅次于直接位于 CPU 中的寄存器。但缺点是,栈中的数据大小与生存周期必须是确定的。
- 堆的优势是可以动态地分配内存大小,生存周期也不必事先告诉编译器,Java 的垃圾回收器会自动收走那些不再使用的数据。但由于要在运行时动态分配内存,存取速度较慢。
3、基本类型的参数传递
众所周知,Java 有 8 种基本数据类型,分别是 int、long、byte、short、float、double 、char 和 boolean。它们的值直接存储在栈中,每当作为参数传递时,都会将原始值(实参)复制一份新的出来,给形参用。形参将会在被调用方法结束时从栈中清除。
请看如下代码:
public class Test {
public static void main(String[] args) {
int age=10;
modify(age);
System.out.println(age);
}
public static void modify(int valueAge) {
valueAge-=3;
}
}
1、main方法中的age类型为基本数据,直接将18存储在栈中
2、调用 modify()
方法的时候,将为实参 age 创建一个副本(形参 valueAge),它的值也为 18,不过是在栈中的其他位置。
3、对形参 age 的任何修改都只会影响它自身而不会影响实参。
4、引用类型传递
不多bb,先看代码:
public class Person {
private String name;
private Integer age;
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
//getter
//setter
}
Person person;
person=new Person("lc", 20);
在person
执行 ‘=’ 操作之前,他仅仅只是一个未被初始化的变量而已。那谁是对象呢?
new Person("lc",20)
,它是对象,存储在堆中;然后 ‘=’ 将对象的引用赋值给了变量person
,于是person
变量拥有了对象在堆中保存的地址。person
将会储存在栈中。
每当该引用类型person
当做参数进行传递时,都会创建一个对象引用(实参)的副本(形参),该形参保存的地址和实参的一致,即都指向同一地址空间。
继续看如下代码:
public class Test1 {
public static void main(String[] args) {
Person a=new Person("lc", 20);
Person b=new Person("lc", 20);
modify(a, b);
System.out.println(a.getAge());
System.out.println(b.getAge());
}
static void modify(Person a1, Person b1) {
a1.setAge(10);
b1 = new Person("lc", 18);
b1.setAge(25);
}
}
结果:
10
20
1、在执行modify()
之前,a,b所执行的对象不一样,尽管创建的内容一样。
2、在执行modify()
方法时,创建的临时变量a1,b1拷贝了实参a,b的引用的地址,即a和a1,b和b1他们指向的是同一地址对象。
3、在modify()
方法中,形参a1变量将age改为10,即指向堆中的地址修改age,即变量a所指向的age也为10。
形参b1重新指向了一个对象,修改其age属性为25。此时b和b1所指向的并不是同一个对象。
4、最后modify()指向完毕时,临时变量a1,b1也会销毁。堆中没有没有被引用的对象将会在合适的时机由jvm自动帮我们销毁。