写在开头
首先简单介绍一下Java中的六大数据存储区:
-
寄存器(register)
因为位于处理器内部,所以是最快的存储区。由编译器根据需求进行分配,我们不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。 -
栈(stack)
位于通用RAM中,存放基本数据类型和引用变量。 -
堆(heap)
也存在于RAM中,存放所有new出来的Java对象。 -
静态区(static storage)
静态区就是内存公共区,存放被static声明的静态变量。内存中静态区的变量可以被本类共享,其他类调用本类静态变量和静态方法时,无需实例化就可以调用。 -
常量池(constant pool)
存放被final声明的字符串常量和基本类型常量, 通常直接存放在程序代码内部。但是在嵌入式系统中,常量本身会和其他部分分离,所以也可以选择将其放在只读存储器(ROM)中。 -
非RAM存储
硬盘等永久存储空间,完全存活于程序之外,可以不受程序的任何控制,在程序没有运行时也可以存在。
下面我们来重点说一下栈和堆。
栈(stack)
用于存储基础数据类型(byte、short、int、long、float、double、boolean、char)和自定义对象的引用。
栈可以通过它的“栈指针”从处理器获得支持。栈指针若向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时候,Java编译器必须知道存储在栈内所有数据的确切大小和生命周期,因为它必须生成相应的代码,以便上下移动栈指针。这一约束限制了程序的灵活性,所以Java对象并不存储于栈中。
当一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间。当该变量退出该作用域后,Java会自动释放掉所分配的内存空间。栈中的数据大小和生命周期是可以确定的,当没有引用指向数据时,这个数据就会消失。
每个线程包含一个栈区,不同的栈中数据都是私有的,其他栈不能访问。但是一个栈中的数据可以共享。假设我们同时定义:
int a = 0;
int b = 0;
编译器会先处理“int a = 0”,它会先在栈中创建一个变量为“a”的引用
然后查找栈中是否有“0”这个值,如果没找到,就将“0”存放进来,然后将“a”指向“0”
然后再处理“int b = 0”,在创建完“b”的引用变量后,发现在栈中已经有“0”这个值,便将“b”直接指向“0”。
这样,就出现了“a”与“b”同时均指向“0”的情况。
这时,如果再令“a = 1”,那么编译器会重新搜索栈中是否有“1”这个值,如果没有,则将“1”存放进来,并令“a”指向“1”;如果已经有了,则直接将“a”指向这个地址。
因此“a”值的改变不会影响到“b”的值。
这种数据的共享与两个对象的引用同时指向一个对象的这种共享是不同的,因为这种情况“a”的修改并不会影响到“b”,它是由编译器完成的,有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
优点:存取速度比堆要快,仅次于寄存器,栈数据可以共享。
缺点:存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆(heap)
堆内存用来存放由new创建的对象和数组。
一个JVM只有一个堆内存,线程是可以共享数据的。在堆中分配的内存由虚拟机的垃圾回收器来管理。
在堆中产生了一个数组或对象后,通常会在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于是为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。
堆相对于栈的优点是:在堆里分配存储有很大的灵活性,因为编译器不需要知道要从堆里分配多少存储区域,也不必知道存储的数据在堆里存活多长时间。当执行创建对象的代码时,会自动在堆里进行存储分配。
不过,为这种灵活性也必须要付出相应的代价,用堆进行存储分配比用栈进行存储存储需要更多的时间。
堆和栈的区别
java中堆和栈的区别自然是面试中的常见问题,总结以下几点:
- 各司其职
最主要的区别就是栈内存用来存储局部变量和方法调用。而堆内存用来存储Java中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。 - 独有还是共享
栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。而堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。 - 异常错误
如果栈内存没有可用的空间存储方法调用和局部变量,JVM会抛出java.lang.StackOverFlowError。
而如果是堆内存没有可用的空间存储生成的对象,JVM会抛出java.lang.OutOfMemoryError。 - 空间大小
栈的内存要远远小于堆内存,如果我们使用递归的话,那么我们的栈很快就会充满。如果递归没有及时跳出,很可能发生StackOverFlowError问题。我们可以通过-Xss选项设置栈内存的大小。-Xms选项可以设置堆的开始时的大小,-Xmx选项可以设置堆的最大值。
这就是Java中堆和栈的区别。理解好这个问题的话,对解决开发中的问题,分析堆内存和栈内存使用,甚至性能调优都有帮助。