JVM内存中堆与栈的比较

Java的堆是一个运行时数据区,类对象从中分配空间。这些对象通过new+构造器等指令建立,它们不需要程序代码来显示的释放。堆是由垃圾回收来负责的,堆的优势是可以动态分配内存大小,生存期也不必实现告诉编译器。因为它是在运行期间动态分配内存的,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是由于要在运行时动态分配内存,存取速度较慢。

栈的优势是存取速度比较快,仅次于寄存器,栈数据可以共享。但缺点是存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。栈中主要存放一些基本类型的变量数据(int ,short,long,byte,float,double,char,boolean)和对象引用。

【1】从存放数据的角度

基本类型的变量 or 引用类型的变量存放在栈中, 堆中存放的是对象or数组对象。

在栈中,引用变量的大小为4个字节,基本类型变量为1-8个字节。

对象的大小和数组的大小是动态的,这也决定了堆中数据的动态性,因为它是在运行时动态分配内存的,生存期也不必在编译时确定,Java 的垃圾收集器会自动收走这些不再使用的数据。

【2】从数据共享的角度

① 在单个线程类,栈中的数据可共享

例如我们定义:

 int a=3;
 int b=3;

编译器先处理int a = 3。

首先它会在栈中创建一个变量为a 的引用,然后查找栈中是否有3 这个值,如果没找到,就将3 存放进来,然后将a 指向3。

接着处理int b = 3;在创建完b 的引用变量后,因为在栈中已经有3这个值,便将b 直接指向3。这样,就出现了a 与b 同时均指向3的情况。

这时,如果再令 a=4 ;那么编译器会重新搜索栈中是否有 4值,如果没有则将 4存放进来,并令 a指向 4;如果已经有了,则直接将 a指向这个地址 。因此 a值的改变不会影响到 b的。

要注意这种数据的共享与两个对象引用同 时指向一个对象的这种共享是不同的。因为这种情况 ,因为这种情况 a的修改并不会影响到 b,它是由编译器完成的,有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另外一个对象的引用变量。

如果我们定义:

Integer a=new Integer(3);//(1)
Integer b=new Integer(3);//(2)

这个时候执行过程为:在执行(1)时,首先在栈中创建一个变量a,然后在堆内存中实例化一个对象,并且将变量a指向这个实例化的对象。在执行(2)时,过程类似,此时,在堆内存中,会有两个Integer类型的对象。

② 在进程的各个线程之间,数据的共享通过堆来实现

例:在多线程开发中,我们的数据共享又是怎么实现的呢?

JVM内存中堆与栈的比较_第1张图片

如图所示,堆中的数据是所有线程栈所共享的,我们可以通过参数传递,将一个堆中的数据传入各个栈的工作内存中,从而实现多个线程间的数据共享(多个进程间的数据共享则需要通过网络传输了。)

【3】从程序设计的的角度

从软件设计的角度看,JVM栈代表了处理逻辑,而JVM堆代表了数据。

这样分开,使得处理逻辑更为清晰,分而治之的思想。这种隔离、模块化的思想在软件设计的方方面面都有体现。

【4】值传递和引用传递的真相

有了以上关于栈和堆的种种了解后,我们很容易就可以知道值传递和引用传递的真相。

程序运行永远都是在JVM栈中进行的,因而参数传递时,只存在传递基本类型和对象引用的问题,不会直接传对象本身。

但是传引用的错觉是如何造成的呢?

在运行JVM栈中,基本类型和引用的处理是一样的,都是传值。所以,如果是传引用的方法调用,也同时可以理解为“传引用值”的传值调用,即引用的处理跟基本类型是完全一样的。

但是当进入被调用方法时,被传递的这个引用的值,被程序解释(或者查找)到JVM堆中的对象,这个时候才对应到真正的对象。

如果此时进行修改,修改的是引用对应的对象,而不是引用本身,即:修改的是JVM堆中的数据。所以这个修改是可以保持的了。

你可能感兴趣的:(深入浅出JVM,深入浅出JVM)