【黑马程序员】-Java基础学习-Java语言的堆内存和栈内存和String类

12/12/2015来源:Java教程人气:1330

------- android培训、java培训、期待与您交流! ---------

  Java语言把内存分为栈内存和堆内存两种。

 基本的数据类型变量和对象的引用变量(也就是说对象在堆内存中的地址)是存储在栈内存中的。其中数组也是对象。当在程序中定义一个变量,JVM就会为这个变量分配一块内存,一旦超过了变量的作用域,JVM就会自动释放掉为该变量分配的内存地址。这就是java语言的垃圾回收机制。

 堆内存用于存放由new创建的对象和数组。在堆中分配的内存,也是由JVM来管理。在堆中产生了一个数组或者对象后,还可以在栈中定义一个特殊的变量,这个变量的取值等于数组或者对象在堆内存中的首地址。

  

1 car a=new car();

  car是一个类,new car()之后就产生了一个对象。a就是存储这个对象的在内存的首地址。

  在栈中的这个特殊的变量就变成了数组或者对象的引用变量,以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于为数组或者对象起的一个别名,或者代号。

  引用变量是普通变量,定义时在栈中分配内存。引用变量在程序运行到作用域外释放。

  而数组和对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在地代码块之外,数组和对象本身占用的堆内存也不会被释放。

  数组和对象在没有引用变量指向它的时候,才变成垃圾,不能再被使用,但是仍然占着内存,在随后的一个不确定的时间被Java垃圾回收机制给释放掉。因此,java程序比较占内存。

  实际上,栈中的变量指向堆内存的变量,是java这门语言的指针。

  栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例.堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放.。

  从堆和栈的功能和作用来通俗的比较,堆主要用来存放对象的,栈主要是用来执行程序的。

  下面用一个例子来说明:

Person p = new Person();
创建一个对象都在内存中做了什么事情?
1:先将硬盘上指定位置的Person.class文件加载进内存。
2:执行main方法时,在栈内存中开辟了main方法的空间(压栈-进栈),然后在main方法的栈区分配了一个变量p。
3:在堆内存中开辟一个实体空间,分配了一个内存首地址值。new
4:在该实体空间中进行属性的空间分配,并进行了默认初始化。
5:对空间中的属性进行显示初始化。
6:进行实体的构造代码块初始化。
7:调用该实体对应的构造函数,进行构造函数初始化。()
8:将首地址赋值给p ,p变量就引用了该实体。(指向了该对象)

  p就是实例化Person类对象的指针,如果仅仅是new Person()就创建了一个匿名对象。如果你仅仅只是对Person类对象的方法调用一次,这个时候就可以创建该类的匿名对象。失去了指针,会在不确定的时候被JVM给清除掉。

  从这里会引申 成员变量和局部变量的区别。

成员变量和局部变量的区别:
1:成员变量直接定义在类中。
   局部变量定义在方法中,参数上,语句中。
2:成员变量在这个类中有效。
局部变量只在自己所属的大括号内有效,大括号结束,局部变量失去作用域。
3:成员变量存在于堆内存中,随着对象的产生而存在,消失而消失。
局部变量存在于栈内存中,随着所属区域的运行而存在,结束而释放。

  这个可以看出,成员变量是在堆内存中的,随着对象的产生而存在,消失而消失。方法中的局部变量存在于栈内存中,方法运行完了局部变量也就出栈了。程序进入CPU运行的是栈内存的数据。

   接下来通过代码来演示下:

代码段A:

1  String str1 =new String ("abc");
2 
3    String str2 =new String ("abc");
4 
5    System.out.PRintln(str1==str2); // false

 

代码段B:

1  String str3 = "abc";
2 
3    String str4 = "abc";
4 
5    System.out.println(str3==str4); //true

代码段C:

1 String str5 = new String("abc");
2 
3  String str6 = "abc";
4 
5 System.out.println(str5==str6);   // false

  String是一个特殊的封装类,存储的是字符串。

  String str = "abc"创建对象的过程:【1】 首先在常量池中查找是否存在内容为"abc"字符串对象;【2】 如果不存在则在常量池中创建"abc",并让str引用该对象;【3】如果存在则直接让str引用该对象。

  "abc"保存在哪里呢?常量池在堆中还是在栈中?

  常量池属于类信息的一部分,而类信息反映到JVM内存模型中是对应存在于JVM内存模型的方法区。也就是说这个类信息 中的常量池概念是存在于在方法区中,而方法区是在JVM内存模型中的堆中由JVM来分配的,所以"abc"可以说存在于堆中(而有些资料,为了把方法区的 堆区别于JVM的堆,把方法区称为栈)。一般这种情况下,"abc"在编译时就被写入字节码中,所以class被加载时,JVM就为"abc"在常量池中 分配内存,所以和静态区差不多。  引

  String str = new String("abc")创建实例的过程:【1】 首先在堆中(不是常量池)创建一个指定的对象"abc",并让str引用指向该对象;【2】 在字符串常量池中查看,是否存在内容为"abc"字符串对象 【3】若存在,则将new出来的字符串对象与字符串常量池中的对象联系起来【4】若不存在,则在字符串常量池中创建一个内容为"abc"的字符串对象,并将堆中的对象与之联系起来。

  因此在代码段B中,str3和str4变量储存的实际是栈中"abc"的内存地址,地址一样所以str3和str4的值一样。所以输出为true。同样的,在代码段A中,每new一次,"abc"字符串存储的位置在堆内存中都不一样,因此str1和str3的值不一样。代码段C中的str5和str6的值也不会一样。

  如果要比较代码段A中str1和str2对象存储的字符串内容是不是一样,应该调用equals()方法,例如:

 1 package blog;
 2 
 3 public class StringDemo {
 4 
 5     public static void main(String[] args) {
 6         String str1 = new String("abc");
 7         String str2 = "abc";
 8         
 9         System.out.println(str1.equals(str2));   // ture
10 
11     }
12 
13 }

  intern 方法可以返回该字符串在常量池中的对象的引用。下面是intern在API上的解释:

intern
public String intern()返回字符串对象的规范化表示形式。 一个初始为空的字符串池,它由类 String 私有地维护。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中的字符串。否则,将此 String 对象添加到池中,并返回此 String 对象的引用。 它遵循以下规则:对于任意两个字符串 s 和 t,当且仅当 s.equals(t) 为 true 时,s.intern() == t.intern() 才为 true。 所有字面值字符串和字符串赋值常量表达式都使用 intern 方法进行操作。字符串字面值在 Java Language Specification 的 §3.10.5 定义。 返回: 一个字符串,内容与此字符串相同,但一定取自具有唯一字符串的池。

  可以通过下面代码来测试一下new出来的和直接赋值的区别。

 1 package blog;
 2 
 3 public class StringDemo { 
 4 
 5                public static void main(String[] args) {         
 6 
 7                 String str1 = "abc";         
 8 
 9                 String str2 = new String("abc").intern();         
10 
11                 System.out.println(str1==str2);     //true
12 
13               } 
14 
15 }

  再补充一个小示例:

1 String str1 = "a"; 
2 String str2 = "bc"; 
3 String str3 = "a"+"bc"; 
4 String str4 = str1+str2; 
5    
6 System.out.println(str3==str4);  //false
7 str4 = (str1+str2).intern(); 
8 System.out.println(str3==str4);  //true

  因为上面的str4是存入的两个变量,是不会放在常量池中的,但是调用intern方法之后,就会去常量池中找有没有"abc", 找到了就会返回其内存地址。