java中堆(stack)和栈(heap)的区别

来源:互联网 发布:u盘在mac上文件不显示 编辑:程序博客网 时间:2024/06/07 22:38

   在学习java的过程中,经常会见到椎和栈的介绍,但是一直都是了解了个大概,而且看了之后又经常会忘掉,所以这次在网上查找了一下资料,把对堆和栈的介绍记录下来,以供复习使用。

    在java中内存的占用主要分为四块:静态区、代码区、堆、栈。其中,堆和栈是使用最多的。

  1. 静态区:内存在程序编译时就分配好的区域,主要存放一些静态变量(static的);
  2. 代码区:存放程序方法的二进制代码,而且是多个对象共享一个代码空间区域;
  3. 堆:堆是一个运行时数据区,主要存放new出来的一些对象和数组。
  4. 栈:栈中主要保存一些基本的数据类型和对象的引用变量。
  5. 常量池:存放一些字符串常量和基本类型常量。

     下面主要说明一下堆、栈的一些运行机制及优缺点和差异:

    在堆中分配的内存,不是由程度员来进行释放的,而是由java虚拟机的垃圾回收机制来自己进行回收的。因为它是运行时动态分配内存的,所以生存期也不必告诉编译区,java的垃圾回收器会自动回收走不再使用的数据。但是,因为是动态分配内存,所以相对的也带来了缺点,那就是:存取速度较慢。

     当一段代码定义了一个变量时,java应在栈中为这个变量分配内存空间,当超过变量作用域时,java会自动释放掉为该变量分配的内存空间,该内存空间可台立即被另做它用。栈的优势是:存取速度快,仅次于CPU中的寄存器,同时,数据可以共享。缺点是:数据大小和生存期必须是确定的,缺乏灵活性。

    假设我们定义:

     a = 3;

     b = 3;

    编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找栈中是否有字面值为3地址,如果没有找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,因为栈中已经有3这个字面值了,便把b直接指向3的地址。这样,就会出现a与b同时指向3的情况。这时,如果再令a = 4;那么编译器会重新搜索栈中是否有4这个字面值的地址,如果没有,则开辟一个存放3这个字面值的地址,然后将a指向4的地址;如果已经有了,则直接将a指向这个地址。因此,a值的改变不会影响b的值。

    但是这种数据共享和两个对象的引用同时指向一个对象的引用是不相同的。如果两个对象的引用同时指向一个对象,如果一个对象的引用变量修改了这个对象的内部状态,那么另一个对象的引用也即刻反映出了这个对象的变化。相反,通过字面值的引用来修改其值,不会发生另一个指向该字面值的引用值也发生改变的情况。例如:上面我们令a和b同时等于3,然后令a等于4,则b还是等于3,不会发生变化。

    String是一个特殊的包装类对象,可以通过String str =  new String("abc");的方式来创建,也可以用String str = "abc";来创建。第一种是通过new来创建的,所以会保存在堆中,每调用一次就会创建一个新的对象。当通过new产生一个字符串时,先去常量池中查看是否有"abc"对象,如果没有,则在常量池中创建一个"abc"对象,然后在堆中创建一个常量池中"abc"的copy对象。在这里曾经有一个面试题问:String str = new String("abc");产生了几个对象,那么答案是:如果常量池中已有"abc"对象,则产生了一个对象,就是堆中的对常量池"abc"的copy对象。如果常量池中没有"abc"对象,则还要在常量池中创建一个"abc"对象,所以就会产生两个对象。而第二种是先在栈中创建一个String对象的引用变量str的引用,然后找到栈中有没有存放值为"abc"的地址,如果没有,则开辟一个存放字面值为"abc"的地址,接着创建一个新的String类的对象o,并将o的字符串值指向这个地址,并且在栈中的这个地址旁边记录下这个引用的对象o,将str指向对象o的地址;如果已经有了值为"abc"的地址,将查找对象o,并返回o的地址。

    比较对象里的值是否相等时,用equals,而测试两个包装类的引用是否指向同一个对象时,用==,例:

        String s1 = "abc";
        String s2 = "abc";
        System.out.println("s1==s2?"+(s1==s2));//true
        System.out.println("s1.equals(s2)?"+s1.equals(s2));//true
        String s3 = new String("abc");
        String s4 = new String("abc");
        System.out.println("s3==s4?"+(s3==s4));//false
        System.out.println("s3.equals(s4)?"+s3.equals(s4));//true
        System.out.println("s1==s4?"+(s1==s4));//false
        System.out.println("s1.equals(s4)?"+s1.equals(s4));//true

   当定义String s1 = "abc";时,编译器在栈中创建一个变量为s1的引用,然后开辟一个存放"abc"字面值的地址,将s1指向"abc"的地址,当处理String s2 = "abc"时,因为栈中已经有值为"abc"的引用,这时会将s2指向这个值为"abc"的引用,所以,当判断对象s1、s2的对象引用是否相同时,因为它们都引用值o "abc"的引用,所以返回true,同时,它们的值引用一样,所以判断值是否相同时,也返回true;

    而当定义String s3 = new String("abc");String s4 = new String("abc");时,因为每new一个对象时,就会有堆中创建一个新的对象,所以当用==判断两个对象是否相等时,会返回false,因为两个对象的值是相等的,所以用equals判断值是,返回true。

0 0
原创粉丝点击