菜鸟笔记
提升您的技术认知

java的对象存储在哪里?

1、寄存器
寄存器是速度最快的存储区域,它位于处理器内部,但它的数量有限,所以要按需分配,不能被人控制。
2、堆栈
通常也叫栈,位于RAM中,堆栈指针向下移动,则分配新的内存;向上移动,则释放那些内存。这种存储方式速度仅次于寄存器。常用于存放对象引用与基本数据类型,不存放Java对象。栈内存被要求存放在其中的数据的大小、生命周期必须是已经确定的。
3、堆
通用的内存池,位于RAM中,用于存放所有的Java对象。编译器不需要知道存储的数据在堆中存活多长时间;当需要一个对象时,用new写一行代码,当执行这行代码时,会自动在堆中进行存储分配,同时,有以上原因,用堆进行存储分配和清理可能比堆栈进行存储分配花更多的时间。
4、常量存储
常量值通常直接存放在程序代码内部,这样做是安全的,因为它们永远不会改变。但有时在嵌入式系统中,常量本身会和其他部分隔开,放在ROM中。
5、非RAM存储
如果数据完全存活在程序之外,那么它可以不受程序控制,程序没有运行时也可以存在。比如流对象与持久化对象。

特殊:基本类型
在Java中,new创建的对象都存放在堆中,但用new创建一个特别小的,简单的变量,就比较麻烦,所以Java采用了与c一样的方法,不用new来创建变量,而是创建一个不是引用的变量,这个变量将值存储在栈中,而且这些变量占的内存空间是确定的,不随机器硬件架构而变化。
Java的数组
在C/C++语言中,数组就是个内存块,超出内存块访问数据或在内存初始化前访问数据是很危险的;
Java会确保数组被初始化,而且不能在它的范围外访问,这种范围检查,以每个数组上少量的内存开销和运行时的下标检查为代价。
当创建一个Java数组时,世纪上创建了一个引用数组,每个引用都会初始化为null。
但创建一个用来存放基本数据类型的数组时,Java也会把它初始化,就算将数组所占的内存全部置为0。

在Java中,栈里面的数据是可以共享的
比如我们定义一个变量:

int a = 1;
int b = 1;

编译器就会先在栈中开辟一块空间存储引用a,然后在栈中查看是否有1这个值存在,发现没有,则在栈中开辟空间存储1这个值;然后再开辟空间存储b,在栈中查看是否有1,发现存在1这个值,那就把b指向1的地址。
用这样数据共享的方式可以节省空间,防止反复的向内存加入同样的值。

Java的String是比较特殊的。
比如这行代码

String s = “123”;
String b = “123”;
System.out.print(s == b);

这段代码先在字符串常量池中查找是否有”123“这个字符串(关于字符串常量池可以看这里),没有的话,则在字符串常量池中创建。很明显,b字符串创建时常量池里已经有123这个字符串了,把它指向123的地址就好了,所以打印出true。

String sc = new String(“123”);
String s = “123”;

这两行代码的区别是:
第一行代码的步骤是
1、先定义一个名为sc的引用
2、然后检索常量池中是否有123的字符串
3、如果不存在,则在字符串常量池中存储进一个值为"abc"的字符串对象。
4、在堆中创建存储一个”abc“字符串对象
5、将对象引用指向堆中的对象
第二行代码和第一行代码的前三步都一样,但后面则是直接将s指向字符串常量池当中的”123“对象。
这里指的注意的是,采用new的方式,虽然是在堆中存储对象,但是也会在存储之前检查常量池中是否已经含有此对象,如果没有,则会先在常量池创建对象,然后在堆中创建这个对象的”拷贝对象“。这也就是为什么有道面试题:String s = new String(“xyz”);产生几个对象?的答案是:一个或两个的原因。因为如果常量池中原来没有”xyz”,就是两个。

最后再转载一个网上看到的一个例子,帮助对栈内存、堆内存的存储进行理解:

class BirthDate {
  
    private int day;
    private int month;
    private int year;
    public BirthDate(int d, int m, int y) {
  
        day = d;
        month = m;
        year = y;
    }
    省略get,set方法………
}

public class Test {
  
    public static void main(String args[]) {
  
        int date = 9;
        Test test = new Test();
        test.change(date);
        BirthDate d1 = new BirthDate(7, 7, 1970);
    }

    public void change1(int i) {
  
        i = 1234;
    }
}

对于以上这段代码,date为局部变量,i,d,m,y都是形参为局部变量,day,month,year为成员变量。下面分析一下代码执行时候的变化:

  1. main方法开始执行:int date = 9;
    date局部变量,基础类型,引用和值都存在栈中。
  2. Test test = new Test();
    test为对象引用,存在栈中,对象(new Test())存在堆中。
  3. test.change(date);
    调用change(int i)方法,i为局部变量,引用和值存在栈中。当方法change执行完成后,i就会从栈中消失。
  4. BirthDate d1= new BirthDate(7,7,1970);
    调用BIrthDate类的构造函数生成对象。
    d1为对象引用,存在栈中;
    对象(new BirthDate())存在堆中;
    其中d,m,y为局部变量存储在栈中,且它们的类型为基础类型,因此它们的数据也存储在栈中;
    day,month,year为BirthDate对象的的成员变量,它们存储在堆中存储的new BirthDate()对象里面;
    当BirthDate构造方法执行完之后,d,m,y将从栈中消失。
  5. main方法执行完之后。
    date变量,test,d1引用将从栈中消失;
    new Test(),new BirthDate()将等待垃圾回收器进行回收。