Java的6种存储储存地址及其解释
- 寄存器(register):这是最快的存储区,因为它位于不同于其他存储区的地方——处理器内部。但是寄存器数量极其有限,所以寄存器根据编译器需求来进行分配,我们无法控制。
- 堆栈(常称为栈:stack):位于通用RAM中。它通过它的“堆栈指针”可以从处理器获得支持。堆栈指针若是往下移动,则分配新的内存,堆栈指针若是往上移动,则释放内存。这是一种快速有效的分配方法,仅次于寄存器。但是创建程序的时候,栈必须要知道存储在栈内数据的大小和确切生命值,因为要根据程序生成代码,以便上下移动堆栈指针。虽然这种存储方式快速,但是也失去了程序的灵活性,所以对象不存放在栈里面。
- 堆(heap):位于通用性的内存池(也位于通用RAM)中,用于存放所有Java的对象。堆不同于栈的好处就是:堆不需要知道自己需要分配多少的内存区域,也不需要知道存储的代码数据能够成活多久,因此,堆的内内存分配有很大的的灵活性。当需要创建一个对象的时候,只需要new一个,执行这行代码的时候堆就会分配内存。但是堆也为了这个灵活性付出了代价的,因为是运行过程中才分配的内存,所以速度就比较慢,比栈要慢很多。
- 静态域(static storage):存放static定义的静态成员,也是程序运行时一直存在的数据。这里的静态是指在固定的位置。
- 常量池(constant storage):存放常量(public static final),即字符串常量、基本数据数据常量。常存放于代码内部,因为不会被改变。
- 非RAM存储:磁盘等永久存储空间
堆、栈、方法区的的存储内容
栈:
- 每个线程都包含一个栈区,栈中只包含基本数据类型的值、对象和基本数据类型的引用。
- 每个栈中的数据(基本数据类型和对象引用)都是私有的,其他栈不能访问
堆:
- 存储的全部是实例对象
- JVM只有一个堆被所有线程共享
方法区:
- 也叫静态区,和堆一样被所有线程共享。
- 方法区包含的都是整个程序永远唯一的元素,如class变量,static变量
栈有一个很重要的特殊性,就是存在栈中的数据可以共享。假如同时定义
int a = 1;
int b = 1;
编译器先处理int a = 1;首先在栈中创建一个int型的变量,然后查找有没有字面值为1的地址。如果没有找到,就开辟一个存放字面值为1的地址,然后a指向1的地址。在创建完a以后,已经存在字面值为1的地址,变量b在栈中找到字面值为1的地址,指向该地址。于是a,b同时指向1。这就是共享。
但是这种引用又与对象引用不相同。通过字面值的引用来修改值,不会导致另一个指向该字面值的引用的值也跟着改变。说起来比较拗口。下面解释一下
int i1 = 9;
int i2 = 9;
int i3 = 9;
public static final int INT1 = 9;
public static final int INT2 = 9;
public static final int INT3 = 9;
当将i2赋值7的时候
i2 = 7;
会继续上面的步骤,在栈中查找有没有字面值为7的地址。如果没有,在栈中开辟一个字面值为7的地址,然后i2指向7的地址,因此不会影响其他引用。
提一下String类,这是一个特殊的包装类数据。
区别一下两种创建方式
1.String str = "abc";
2.String str = new String("abc");
创建方式1:
- 先定义一个名为str的String类的对象引用变量:String str;
- 在栈中查找有没有存放值为“abc”的地址,如果没有,就开辟一个存放字面值为“abc”的地址,接着新建一个String的对象string指向该地址,在栈中这个地址旁记下这个引用的对象string。如果有值为“abc”的地址,则查找对象string,并返回string的地址。
- 将str指向引用变量string的地址
- 也许你会觉得和 基本数据类型的引用 类似,没错就这么理解吧。
String str1 = "字符串";
String str3 = "字符串";
System.out.println(str1 == str3);//true
- 这里只创建了一个对象,但是两个引用变量指向了这个对象。
创建方式2:
- new出来的对象都存放在堆里面,显而易见,此时两者已经地址已经不相等了
- 下面代码创建了三个对象,自行理解~
String str1 = "字符串";
String str2 = new String("字符串");
String str3 = new String("字符串");
System.out.println(str1 == str2);//false
System.out.println(str3 == str2);//false