Contents
  1. 1. java存储数据的6个地方
  2. 2. 栈内存和堆内存
  3. 3. java数据类型
    1. 3.0.1. 基本数据类型
    2. 3.0.2. 包装类类型
    3. 3.0.3. 为什么会存在两种类型?
    4. 3.0.4. 二者之间的区别
  • 4. 总结栈内存和堆内存
  • 参考文献
    博客1 博客2

    java存储数据的6个地方

    1. 寄存器(register)
      最快的存储区域,位于处理器内部,不同于其他存储区。但是存储器的数量及其有限,所以寄存器由编译器根据需求进行分配。不能直接控制,也不能在程序中感觉到寄存器存在的任何迹象。

      —– 最快的存储区,由编译器根据需求进行分配,程序无法控制
    2. 堆栈(stack)
      位于通用RAM中,但通过”堆栈指针”可以从处理器哪里获得支持。堆栈指针向下移动,则分配新的内存;若向上移动,则释放那些内存。这是一种快速有效的分配存储方法,仅次于寄存器。创建程序时,java编译器必须知道存储在堆栈内所有数据的确切大小和生命周期,因为它们必须生成相应的代码。以便上下移动堆栈指针,这一约束限制了程序的灵活性,所以虽然某些JAVA数据存储在堆栈中——特别是对象引用,但是JAVA对象不存储其 中。具体方法结束后,系统自动释放JVM内存资源

      —– 存放基本类型的变量数据和对象,数组的引用,但对象本身不存放在栈中,而是 存放在堆(new 对象)或者常量池中(字符串常量存放在常量池中)。
    3. 堆(heap)

      一种通用的内存池(也存在于RAM中),用于存放所有的java对象。堆内存不同于堆栈的好处是:编译器不需要知道从堆里分配多少存储区,也不必知道存储数据在堆里面存活时间。因此,堆的灵活性较强。当你需要创建一个对象的时候,只需要new写一行简单的代码,当执行 这行代码时,会自动在堆里进行存储分配。当然,为这种灵活性必须要付出相应的代码。用堆进行存储分配比用堆栈进行存储存储需要更多的时间。一般由程序员分配释放,jvm不定时查看这个对象,如果没有引用指向这个对象就回收

    —– 存放所有new 出来的对象

    1. 静态存储(static storage)–数据区

      这里的“静态”是指“在固定的位置”。静态存储里存放程序运行时一直存在的数据。你可用关键字static来标识一个对象的特定元素是静态的,但JAVA对象本身从来不会存放在静态存储空间里

      —– 存放静态成员(static 定义的)
    2. 常量存储(constant storage)

      常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会被改变。有时,在嵌入式系统中,常量本身会和其他部分分割离开,所以在这种情况下,可以选择将其放在ROM中 。常量池存储于堆中。

      ——存放字符串常量和基本类型常量(public static final)和对其他类型、方法、字段的符号引用
    3. 非RAM存储

    ​ 如果数据完全存活于程序之外,那么它可以不受程序的任何控制,在程序没有运行时也可以存在。

    —–硬盘等永久存储空间

    就速度而言,有如下关系:寄存器 >堆栈 > 堆 > 其它

    下图大致描述了java的内存分配:

    img

    数据段:存放定义的static 定义的静态成员。

    常量区:JVM为每个已加载的类型维护一个常量池,常量池就是这个类型用到的常量的一个有序集合。

    代码段:存放程序中的二进制代码,而且多个对象共享一个代码空间区域

    栈内存和堆内存

    1. java中栈和常量池中的对象可以共享,对于堆中的对象不可以共享。

    2. 堆栈中的数据大小和生命周期可以确定,当没有引用指向数据时,这个数据就会消失。堆中的对象的由垃圾回收器负责回收,因此大小和生命周期不需要确定,具有很大的灵活性。(堆最大的优势可以在运行期间动态地分配内存大小)

    3. 对于字符串:其对象的引用都是存储在栈中的,如果是编译期已经创建好(直接用双引号定义的)的就存储在常量池中,如果是运行期(new出来的)才能确定的就存储在堆中。对于equals相等的字符串,在常量池中永远只有一份,在堆中有多份。
      如下代码:java代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      String s1 = "china"; 

      String s2 = "china";

      String s3 = "china";

      String ss1 = new String("china");

      String ss2 = new String("china");

      String ss3 = new String("china");

    img

    ​ 这里解释一下,对于通过 new 产生一个字符串(假设为 ”china” )时,会先去常量池中查找是否已经有了 ”china” 对象,如果没有则在常量池中创建一个此字符串对象,然后堆中再创建一个常量池中此 ”china” 对象的拷贝对象。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      public class TestStringConstant {
    public static void main(String args[]) {
    // 字符串常量,分配在常量池中,编译器会对其进行优化, Interned table
    // 即当一个字符串已经存在时,不再重复创建一个相同的对象,而是直接将s2也指向"hello".
    String s1 = "hello";
    String s2 = "hello";
    // new出来的对象,分配在heap中.s3与s4虽然它们指向的字符串内容是相同的,但是是两个不同的对象.
    // 因此==进行比较时,其所存的引用是不同的,故不会相等
    String s3 = new String("world");
    String s4 = new String("world");

    System.out.println(s1 == s2); // true
    System.out.println(s3 == s4); // false
    System.out.println(s3.equals(s4)); // true
    // String中equals方法已经被重写过,比较的是内容是否相等.
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    1
    String a = "ab";
    String bb = "b";
    String b = "a" + bb;
    System.out.println((a == b)); //result = false
    2
    String a = "ab";
    final String bb = "b";
    String b = "a" + bb;
    System.out.println((a == b)); //result = true
    3
    String a = "ab";
    final String bb = getBB();
    String b = "a" + bb;
    System.out.println((a == b)); //result = false
    private static String getBB() {
    return "b";
    }
    分析:
    1】中,JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,即"a" + bb无法被编译器优化,只有在程序运行期来动态分配并将连接后的新地址赋给b。所以上面程序的结果也就为false。 
    2】和【1】中唯一不同的是bb字符串加了final修饰,对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到自己的常量池中或嵌入到它的字节码流中。所以此时的"a" + bb和"a" + "b"效果是一样的。故上面程序的结果为true
    3】JVM对于字符串引用bb,它的值在编译期无法确定,只有在程序运行期调用方法后,将方法的返回值和"a"来动态连接并分配地址为b,故上面程序的结果为false

    4.对于基础类型的变量和常量:变量和引用存储在栈中,常量存储在常量池中。

    引申问题:对于成员变量和局部变量:成员变量就是方法之外,类内部定义的变量;局部变量就是方法或语句内部定义的变量。局部变量必须初始化。形参是局部变量,局部变量的数据存在栈内存中,栈内存中的局部变量随着方法的消失而消失。成员变量存储在堆中的对象中,由垃圾回收机制负责回收。

    java数据类型

    1. 基本数据类型

      int short long byte char float dubble boolean
      这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。值得注意的是,自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出 后,字段值就消失了),出于追求速度的原因,就存在于栈中。 另外,栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义 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的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存 放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

    2. 包装类类型

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

    3. 为什么会存在两种类型?

      我们都知道在Java语言中,new一个对象存储在堆里,我们通过堆栈中的引用来使用这些对象;但是对于经常用到的一系列类型如int,如果我们用new将其存储在堆里就不是很有效——特别是简单的小的变量。
      所以就出现了基本类型,同C++一样,Java采用了相似的做法,对于这些类型不是用new关键字来创建,而是直接将变量的值存储在堆栈中,因此更加高效。

      有了基本类型之后为什么还要有包装器类型呢?我们知道Java是一个面相对象的编程语言,基本类型并不具有对象的性质,为了让基本类型也具有对象的特征,就出现了包装类型(如我们在使用集合类型Collection时就一定要使用包装类型而非基本类型),它相当于将基本类型“包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

    4. 二者之间的区别

      1.声明方式不同,基本类型不适用new关键字,而包装类型需要使用new关键字来在堆中分配存储空间;

      2.存储方式及位置不同,基本类型是直接将变量值存储在堆栈中,而包装类型是将对象放在堆中,然后通过引用来使用;

      3.初始值不同,基本类型的初始值如int为0,boolean为false,而包装类型的初始值为null

      4.使用方式不同,基本类型直接赋值直接使用就好,而包装类型在集合如Collection、Map时会使用到。

      5.当比较包装类里面的数值是否相等时,用equals()方法;当测试两个包装类的引用是否指向同一个对象时,用==。

    总结栈内存和堆内存

    1.存储数据:栈用于存储基本数据类型和对象,数组的引用;堆存储所有new出来的对象。
    2.灵活性:堆栈中的存储数据数据大小和生命周期都确定,当没有引用指向数据,数据就会消失。堆中的数据是在运行期间动态创建,因此数据大小和生命周期不确定。也因此灵活性更强。
    3.回收机制:栈中数据在具体方法结束后,jvm自动释放资源;一般由程序员释放,jvm定期查看,如果没有引用指向该对象就回收。

    4.共享性:栈和常量池中的数据共享,堆中的数据不共享。同一份数据在栈或常量池中只能有一份,堆中可以有多分。

    Contents
    1. 1. java存储数据的6个地方
    2. 2. 栈内存和堆内存
    3. 3. java数据类型
      1. 3.0.1. 基本数据类型
      2. 3.0.2. 包装类类型
      3. 3.0.3. 为什么会存在两种类型?
      4. 3.0.4. 二者之间的区别
  • 4. 总结栈内存和堆内存