JAVA中的堆与栈

在Java语言中,堆与栈都是在内存中存放变量的地方。

在堆中,主要存放的是引用类型的变量(除了基本的变量类型:boolean(1), byte(8), char(16), short(16), int(32), long(64), float(32), double(64), void(-),所以 String类型变量,数组都是引用型变量),也就是在JAVA的运行过程中,通过new()创建的对象。

栈通常存放的各个类型的变量,例如基本类型的变量和引用类型的变量,变量出了作用的代码块就会释放。

堆和栈的区别:

栈负责保存代码执行(或调用)的路径,而堆负责保存对象(或者数据)的路径。有的,文章介绍中将栈想象成一个自顶向下放的盒子,每调用一次方法时,就会将应用程序中所发生的事件记录在最顶层的盒子里,每次使用的时候都是先使用最顶层的盒子。堆就像床上的旧衣服,可以随便拿来用,而栈不一样,必须从上到下依次使用(引自:http://www.c-sharpcorner.com/UploadFile/rmcochran/csharp_memory01122006130034PM/csharp_memory.aspx)。


例如:

public class Car{

private String Color;  //引用变量

private int weight;//基本的变量

       public Car(String color,int weight )

{

Color=color;

weight=weight;

}

}

public class testClass{

public static void main(String )

{

Car Honda=new Honda("Black",2.3);

System.out.println(Honda.Color,Honda.weight);

}

}

在上述例子中,变量 Color, weight, Honda都存放在栈上,其中,由于String 和Honda是引用型对象(在运行过程中,通过new()来产生),因此在堆上为他们分配了空间存放具体数据,而在栈上分配以块内存空间存放的String 和Honda变量在对堆上对应值的地址。

当在一块代码段中,定义一个变量时,JAVA就会在栈中为这个变量分配内存空间,当该变量超出作用域时,JAVA会自动释放为该变量创建的内存空间。在栈中,如果是基本数据类型,如 int a=3, 则 为a分配的内存空间中存放的就是字面值(3),如果是引用类型,如 Honda则为它分配的内存空间中存放的是Honda数据在堆上的地址。(变量是栈地址的别名)

总结:

我们可以简单的理解:当为基本类型的变量时,栈中的数据就是变量的字面值。当这两个变量的值相同时,栈也会重复存放一份,所以改变一个变量的值,另一个变量的值不会发生改变。但是当栈中存放的是引用类型的变量时,存放的是该引用变量在堆内存中的地址,如果两个引用类型的变量值相同,他们指向的是同一块堆内存,所以对一个引用类型变量值的改变,就会改变堆中存放的值,因此另一个变量的值也会发生改变。

接下来,我们用例子来演示下,值传递和引用传递的区别,从而对引用类型有个清晰的理解:

值传递:

Code:

JAVA中的堆与栈

Result:

JAVA中的堆与栈

发现了没?在值传递的时候,主方法和从方法中的a值并不一样,add()方法只是改变了从方法中的a值,主方法的a值并没有发生改变。

接下来,我们分析下程序的执行过程:

当执行到第7行时,首先产生个Sub类的对象sub,由于该类中没有成员变量,不会将成员变量入栈,只会将sub对象和其成员方法add()入栈,同时堆为sub对象分配一块空间。

第8行,程序定义了变量a=10,此时,程序会在栈上申请一块内存名为a,其中存放的是10,继续执行。

第9行,将a传递到Sub类的add()方法,在栈中发生的事是,a出栈,add()方法出栈,执行a+1,此时栈的顶层会产生一个内存空间,用来存放a+1的结果11。注意,这是和主方法中的a是两个不同的内存空间,因此互不影响,而且在加法的操作过程中没有涉及到堆上的内容。

因此,在值传递过程中,程序会在栈上重新分配一块内存空间,用来存放相加后的值。

但是,如果我们非要利用值传递实现相加的效果,可以吗?可以,我们只需要将add()方法中的值返回给主类,让其接收到即可,如下代码:

JAVA中的堆与栈

result:

JAVA中的堆与栈

这样就在main方法中,输出了Sub类add()方法的值。

引用传递:

code:

JAVA中的堆与栈

Result:

JAVA中的堆与栈

我们可以发现,在main主方法中的结果和从方法中的结果是一样的。

接下来,我们来分析下执行的过程:

首先,当程序执行到第14行时,产生Sub的一个实例sub,并在堆上为其成员变量a,分配一个内存空间,将地址赋值给a,同时在栈上为引用变量a分配一个内存空间;

第15行将sub的引用变量a所引用的值改变为4,也就是a中存放的堆地址内的值改变为4,

第16行,将sub的引用传递给add(),在add()方法中,将a所引用的值改为40。

有上述的分析过程来看,对于a我们只是建立了一个堆,并将堆地址赋值给a,所有的引用传递,传递的就是地址,因此我们的操作也是地址中的内容,所以会相同。有一new()就会在堆上新建一块内存。