文章开始把我喜欢的这句话送个大家:这个世界上还有什么比自己写的代码运行在一亿人的电脑上更酷的事情吗,如果有那就是让这个数字再扩大十倍。
1.栈内存
位于RAM当中,通过堆栈指针可以从处理器获得直接支持。堆栈指针向下移动,则分配新的内存;向上移动,则释放那些内存。这种存储方式速度仅次于寄存器。
(常用于存放对象引用和基本数据类型,而不用于存储对象)
2.堆内存
一种通用的内存池,也位于RAM当中。其中存放的数据由JVM自动进行管理。
堆相对于栈的好处来说:编译器不需要知道存储的数据在堆里存活多长。当需要一个对象时,使用new写一行代码,当执行这行代码时,会自动在堆里进行存储分配。同时,因为以上原因,用堆进行数据的存储分配和清理,需要花费更多的时间。
3.常量池
常量(字符串常量和基本类型常量)通常直接存储在程序代码内部(常量池)。这样做是安全的,因为它们的值在初始化时就已经被确定,并不会被改变。常量池在java用于保存在编译期已确定的,已编译的class文件中的一份数据。它包括了关于类,方法,接口等中的常量,也包括字符串常量,如String s = "java"这种声明方式
相同之处:
堆与栈都是用于程序中的数据在RAM(内存)上的存储区域。并且Java会自动地管理堆和栈,不能人为去直接设置。
不同之处:
1.存储数据类型:栈内存中存放局部变量(基本数据类型和对象引用),而堆内存用于存放对象(实体)。
2.存储速度:就存储速度而言,栈内存的存储分配与清理速度更快于堆,并且栈内存的存储速度仅次于直接位于处理器当中的寄存器。
3.灵活性:就灵活性而言,由于栈内存与堆内存存储机制的不同,堆内存灵活性更优于栈内存。
这样两种存储方式的不同之处,也是由于它们自身的存储机制所造成的。所以为了理解它们,首先我们应该弄清楚它们分别的存储原理和机制,在Java中:
— 栈内存被要求存放在其中的数据的大小、生命周期必须是已经确定的;
— 堆内存可以被虚拟机动态的分配内存大小,无需事先告诉编译器的数据的大小、生命周期等相关信息。
栈内存和堆内存的存储数据类型为何不同?
我们知道在Java中,变量的类型通常分为:基本数据类型变量和对象引用变量。
首先,8种基本数据类型中的数字类型实际上都是存储的一组位数(所占bit位)不同的二进制数据;除此之外,布尔型只有true和false两种可能值。
其次,对象引用变量存储的,实际是其所关联(指向)对象在内存中的内存地址,而内存地址实际上也是一串二进制的数据。
所以,局部变量的大小是可以被确定的;
接下来,java中,局部变量会在其自身所属方法(或代码块)执行完毕后,被自动释放。
所以局部变量的生命周期也是可以被确定的。
那么,既然局部变量的大小和生命周期都可以被确定,完全符合栈内存的存储特点。自然,局部变量被存放在栈内存中。
String s1 = "abc";
String s2 = "abc";
System.out.print(s1==s2);
打印的结果为true。
首先String s = "abc"和String s = new String("abc")两张声明方式的不同之处:
如果是使用String s = "abc"这种形式,也就是直接用双引号定义的形式。
可以看做我们声明了一个值为”abc“的字符串对象引用变量s。
但是,由于String类是final的,所以事实上,可以看做是声明了一个字符串引用常量。存放在常量池中。
如果是使用关键字new这种形式声明出的,则是在程序运行期被动态创建,存放在堆中。
所以,对于字符串而言,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中;
如果是运行期(new出来的)才能确定的就存储在堆中。
对于equals相等的字符串,在常量池中永远只有一份,在堆中可以有多份。
String s = ”abc“的工作过程可以分为以下几个步骤:
(1)定义了一个名为"s"的String类型的引用。
(2)检查在常量池中是否存在值为"abc"的字符串对象;
(3)如果不存在,则在常量池(字符串池)创建存储进一个值为"abc"的字符串对象。如果已经存在,则跳过这一步工作。
(4)将对象引用s指向字符串池当中的”abc“对象。
String s = new String(”abc“)的步骤则为:
(1)定义了一个名为"s"的String类型的引用。
(2)检查在常量池中是否存在值为"abc"的字符串对象;
(3)如果不存在,则在常量池(字符串池)存储进一个值为"abc"的字符串对象。如果已经存在,则跳过这一步工作。
(4)在堆中创建存储一个”abc“字符串对象。(5)将对象引用指向堆中的对象。
这里指的注意的是,采用new的方式,虽然是在堆中存储对象,但是也会在存储之前检查常量池中是否已经含有此对象,如果没有,则会先在常量池创建对象,然后在堆中创建这个对象的”拷贝对象“。这也就是为什么有道面试题:String s = new String(“xyz”);产生几个对象?的答案是:一个或两个的原因。因为如果常量池中原来没有”xyz”,就是两个。
再看上面的例子,在执行String s1 = 'abc"时;常量池中还没有对象,所以创建一个对象。之后在执行String s2 = 'abc"的时候,因为常量池中已经存在了"abc'对象,所以说s2只需要指向这个对象就完成工作了。那么s1和s2指向同一个对象,用”==“比较自然返回true。所以常量池与栈内存一样,也可以实现数据共享。
还有值得注意的一点的就是:我们知道局部变量存储于栈内存当中。那么成员变量呢?答案是:成员变量的数据存储于堆中该成员变量所属的对象里面。
而栈内存与堆内存的另一不同点在于,堆内存中存放的变量都会进行默认初始化,而栈内存中存放的变量却不会。
这也就是为什么,我们在声明一个成员变量时,可以不用对其进行初始化赋值。而如果声明一个局部变量却未进行初始赋值,如果想对其进行使用就会报编译异常的原因了。
加油吧,程序员!