Java中堆、栈和常量池的区别
栈 堆 常量池的概念
首先我们先了解一下概念,Java把内存分成两种,一种叫做栈内存,一种叫做堆内存。
栈内存
存放基本类型的变量数据和对象类型的引用(请注意存放的是引用),对象本身不存放在栈中,而是存放在堆(new 出来的对象)或者常量池中(字符串常量对象存放在常量池中)
堆内存
堆内存用来存放由new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理
常量池
存放字符串常量和基本类型常量(public static final)
堆栈的比较
一般来说,栈的速度比堆的速度要快,那为什么还要引入堆呢?这就要涉及到堆栈的优缺点了。
栈的优势是,存取速度比堆要快,仅次于寄存器,栈数据可以共享(指的是线程共享,而给进程共享)。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要 在运行时动态分配内存,存取速度较慢。
##奇怪的问题
在学习java堆栈的知识时,也遇到一点有趣的小问题,我很奇怪,查了很多资料后发现有不同的说法。
对于String s=new String("abc");的创建
一种说法:对于通过new产生一个字符串(假设为”abc”)时,会先去常量池中查找是否已经有了”abc”对象,如果已经创建"abc",则不会继续创建。如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此”abc”对象的拷贝对象。一个是编译时决定的,最后放在常量池中。一个是运行时放在堆里面的。
另一种说法:1、首先在堆中(不是常量池)创建一个指定的对象"abc",并让str引用指向该对象 2、在字符串常量池中查看,是否存在内容为"abc"字符串对象 3、若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来 4、若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来 intern 方法可以返回该字符串在常量池中的对象的引用
String str1 = "abc";
一种说法:
栈中开辟一块空间存放引用str1,
String池中开辟一块空间,存放String常量"abc",
引用str1指向常量池中String常量"abc",
str1所指代的地址即常量"abc"所在地址,输出为true
另一种说法:先在栈中创建一个对String类的对象引用变量str1 ,然后查找栈中有没有存放"abc",如果没有,则将"abc"存放进栈,并令str1 指向"abc",如果已经有"abc",则直接令str1 指向"abc"
对于上述两个问题的两种说法,本人均更倾向于第一种说法,因为第一种说法对程序有优化的作用,第二种说法是多此一举,并未带来任何好处。
讨论:int a=1;那么这个1是存在栈里还是存在常量池?
对于这个问题我也困惑了好久,首先可以肯定的a是int类型的引用,存储在栈里,那么这个1到底是存在哪里呢? 我们通常的理解是1作为一个常量肯定是存储在常量池中啊。在查阅了很多资料后发现,这个1是存储在栈中,并未存储在常量池中。 这里我们需要注意的是对于1我们看起来是常量,但j虚拟机却并不是将其看作是常量而是一个变量,只有声明为final类型的数据JVM才会将其看做是常量,而常量池是存放字符串常量和基本类型常量(public static final)的,所以这里的1是存储在栈里的。
实例分析
我们举一个最常见的例子,分析一下栈基本的处理过程:
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,它是由编译器完成的,它有利于节省空间。而一个对象引用变量修改了这个对象的内部状态,会影响到另一个对象引用变量。
总结以及需要注意的地方
对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。
对于基本类型应该不会用到常量池,因为基本类型的值就存在栈中,在Java中对基本类型变量的运算、判断以及赋值都是对值的操作,没有对地址操作,例如:int a = 1; int b = 2; a = b; 这是将b的值赋给a,而不是a引用b;
我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。请注意,对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。因 此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。