了解栈内存堆内存

由于 java 有垃圾回收机制,所以往往不太会去关注栈堆的内存分配问题,直至OOM

一、了解栈堆概念

1、堆【存储对象创建实例】

  1. 程序开始运行时,JVM从OS获取一些内存,部分是堆内存。堆内存通常在存储地址的底层,向上排列。
  2. 堆是一个"运行时"数据区,类实例化的对象就是从堆上去分配空间的;
  3. 在堆上分配空间是通过"new"等指令建立的,堆是动态分配的内存大小,生存期也不必事先告诉编译器;
  4. 与C++不同的是,Java自动管理堆和栈,垃圾回收器可以自动回收不再使用的堆内存;
  5. 缺点是,由于要在运行时动态分配内存,所以内存的存取速度较慢。

2、栈【存放基本类型和引用类型】

  1. 先进后出的数据结构,通常用于保存方法中的参数,局部变量;
  2. 在java中,所有基本类型(short,int, long, byte, float, double,boolean, char)和引用类型的变量都在栈中存储;
  3. 栈中数据的生存空间一般在当前scopes内(由{…}括起来的区域;
  4. 栈的存取速度比堆要快,仅次于直接位于CPU中的寄存器;
  5. 栈中的数据可以共享,多个引用可以指向同一个地址;
  6. 缺点是,栈的数据大小与生存期必须是确定的,缺乏灵活性。

3、总结

  1. Java堆内存是操作系统分配给JVM的内存的一部分。
  2. 当我们创建对象时,它们存储在Java堆内存中。
  3. 你可以通过用JVM的命令行选项 -Xms, -Xmx, -Xmn来调整Java堆空间的大小。
  4. 栈中存放的基本类型数据:short,int, long, byte, float, double,boolean, char
  5. 只要采用 new 的形式创建对象,那么就存储在 java 堆内存中。

二、栈中的数据存储

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 == b true

2、包装类数据存储

如Integer, Double, String等将相应的基本数据类型包装起来的类。
这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

以String为例

String是一个特殊的包装类数据。即可以用String str = new String("abc");的形式来创建,也可以用String str = "abc";的形式来创建。
前者是规范的类的创建过程,即在Java中,一切都是对象,而对象是类的实例,全部通过new()的形式来创建。

那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。

关于String str = "abc"的内部工作。Java内部将此语句转化为以下几个步骤:

  • a、先定义一个名为str的对String类的对象引用变量:String str;
  • b、在栈中查找有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象O,并将O的字符串值指向这个地址,而且在栈中这个地址旁边记下这个引用的对象O。如果已经有了值为"abc"的地址,则查找对象O,并返回O的地址。
  • c、将str指向对象O的地址。
     值得注意的是,通常String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了一个指向存在栈中数据的引用(即:String str = "abc";既有栈存储,又有堆存储)。

为了更好地说明这个问题,我们可以通过以下的几个代码进行验证。

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); //true

只有在两个引用都指向了同一个对象时才返回真值。str1与str2是否都指向了同一个对象)

结果说明,JVM创建了两个引用str1和str2,但只创建了一个对象,而且两个引用都指向了这个对象。

3、总结

(1). 我们在使用诸如String str = "abc";的格式定义类时,总是想当然地认为,我们创建了String类的对象str。

担心陷阱!对象可能并没有被创建!唯一可以肯定的是,指向 String类的引用被创建了。
至于这个引用到底是否指向了一个新的对象,必须根据上下文来考虑,除非你通过new()方法来显要地创建一个新的对象。
因此,更为准确的说法是,我们创建了一个指向String类的对象的引用变量str,这个对象引用变量指向了某个值为"abc"的String类。
清醒地认识到这一点对排除程序中难以发现的bug是很有帮助的。

(2). 使用String str = "abc";的方式,可以在一定程度上提高程序的运行速度,因为JVM会自动根据栈中数据的实际情况来决定是否有必要创建新对象。
而对于String str = new String("abc");的代码,则一概在堆中创建新对象,而不管其字符串值是否相等,是否有必要创建新对象,从而加重了程序的负担。

(3). 由于String类的immutable性质(因为包装类的值不可修改),当String变量需要经常变换其值时,应该考虑使用StringBuffer类,以提高程序效率。

4、实例

String s = new String("abc") 创建了几个对象?

首先我们要明白两个概念,引用变量和对象,对象一般通过new在堆中创建,s只是一个引用变量。

答案2个。

5、知识点

我们可以用==判断一下两个引用变量是否指向了一个地址即一个对象

posted @ 2019-06-25 18:10  niceyoo  阅读(1713)  评论(0编辑  收藏  举报