不同类型的常,变量在JVM中的存储位置和equals与==的区别
1.简单了解JVM中内存分配(对于jdk1.8来说)
1)JVM内存结构
JVM的运行时数据区主要包括堆,栈,方法区和程序计数器,其中栈分为虚拟机栈和本地方法栈。
A)程序计数器:
简单来说程序计数器是当前线程所执行字节码的行号指示器,一个线程的执行是通过字节码解释器改变当前线程的计数器的值,来获取下一条需要执行的字节码指令,从而确保线程正确执行。
每一个线程都有一个独立的程序计数器,程序计数器是线程私有的内存。
B)栈:
JVM中的栈有虚拟机栈和本地方法栈,我们常说的堆和栈中的栈是指虚拟机栈,下文所有的栈都是指虚拟机栈。
a)虚拟机栈:
限定仅在表头进行插入和删除操作的线性表,即入栈和出栈都是对栈顶元素进行操作的,所以栈是后进先出原则。
栈是线程私有的,每一个线程都有独立的栈空间。
每个方法在执行时都会创建一个栈帧,栈帧中存储了局部变量表,操作数栈,动态连接和方法出口等。每一个方法从调用到运行结束的过程都对应一个栈帧从栈中入栈到出栈。
局部变量表:
局部变量表中存储了基本数据类型的局部变量(包括参数),对象的引用(不包含对象的内容),局部变量表所需的内存空间在编译期间完成分配,在方法运行期间不会改变大小。
操作数栈:
操作数栈是一个后进先出的栈,其中的元素可以是任意的java数据类型。操作数栈可以进行算数运算和调用其他方法时进行参数传递,操作数栈可以理解为栈帧中用于计算的临时数据存储区。
b)本地方法栈:
本地方法栈是为Native方法服务的,JDK中有很多方法是用Native修饰的,这些方法都不是用Java语言实现的,而是已本地语言(如C或者C++)。
C)堆:
堆是java虚拟机所管理的内存最大的一块存储区域。堆内存被所有的线程共享。主要存放使用new关键字创建的对象,所有对象实例和数组都要在堆上分配。
D)元数据:
a)jdk1.7之前,方法区位于永久代,永久代和堆相互隔离,永久代的大小在启动JVM时可设置一个固定值,不可变。
b)jdk1.8中,保留了方法区的概念,取消永久代,方法存放于元空间中,元空间和堆任然不相连,但是与堆共享物理内存,逻辑上可以认为在堆中。
符号引用转移到本地内存,不是虚拟机中。
字符串常量池和类的静态变量转移到堆中。
常量池:
储存编译器生成的各种字面量和符号引用。字面量就是java常量,比如文本字符串,final修饰的常量等,符号引用包括类和接口的全限定名,方法名和描述符,字段名和描述符等。
常量池避免了频繁创建和销毁对象而影响性能,实现了对象共享。
2)分清楚常量和变量以及变量类型:
常量:1.被final修饰的成员变量。2.值的本身就是常量,比如System.out.println(1);中的1.
是常量或者变量以及是什么类型的变量和他是基本类型数据还是引用类型数据无关。
public class Demo {
//常量
public final double aaa=1.1;
//静态常量
public static final double bbb=1.1;
//类变量(静态变量) 可以直接被类使用,不需创建对象
public static int age = 1;
//成员变量(非静态变量) 只能先创建对象,再用对象调用
public int money = 10;
public static void getScore() {
//不可变变量
final int a=1;
//局部变量 函数中的变量
int score = 100;
}
}
3)分清楚基本类型和引用类型:
基本类型:Java 提供了八种基本类型 byte–short–int–long–float–double–boolean–char。
引用类型:接口(interface),类(class),数组(array),这里要注意Integer,String等是包装类(他们都是类)。变量名存储了一个内存地址值,该内存地址值指向所引用的对象。
//对于基本类型来说aaa是变量名,1是值。
int aaa=1;
//对于引用类型来说,bbb是引用变量名,new Demo()是对象。
Demo bbb=new Demo();
4)分清各种类型的存储位置:
A)常量:
a)基本变量:变量名和值都放在常量池中,在元数据中。
b)引用变量:变量名和值都放在常量池中,在元数据中。
B) 静态常量:
a)基本变量:变量名和值都放在常量池中,在元数据中。
b)引用变量:变量名和值都放在常量池中,在元数据中。
C)静态变量:
a)基本变量:变量名和值都放在堆中。
b)引用变量:变量名和值都放在堆中。
D)成员变量:
a)基本变量:变量名和值都放在堆中。
b)引用变量:变量名和值都放在堆中。
E)局部变量:
a)基本变量:变量名和值都放在虚拟机栈中栈帧的局部变量表中。
b)引用变量:引用变量名放在虚拟机栈中栈帧的局部变量表中,对象放在堆中。
字符串除外,字符串类型不论是什么类型变量都存放在字符串变量池中,在堆中。
2.==的作用
1)对于基本类型来说,==是判断 两个变量是否指向同一个栈内存,也就相当于比较变量的值是否相同。
2)对于引用类型来说,==是判断两个对象或者实例是否指向同一个堆内存,即两个对象的地址是否相同。
3.表达式创建对象和new对象的区别
1)对于Integer,String等引用类型,我们可以通过表达式和直接new这两种方法创建对象
public static void main(String[] args) {
//表达式方式创建对象
Integer a=1;
String s1="小明";
//new对象
Integer b=new Integer(1);
String s2=new String("小明");
}
对于除包装类外的引用类型:
当我们创建一个局部变量对象(new Object)时,会调用对象的构造函数来开辟空间,将对象数据储存到堆内存中,与此同时,在栈内存中生成对应的引用,我们在后续代码中调用的都是栈内存中的引用。
对于包装类的引用类型来说:
采用表达式创建对象时(比如String s1=“小明”;),JVM首先会去常量池中查找是否存在"小明"这个对象,如果存在就不会创建任何对象,直接把池中的"小明"这个对象的地址返回给s1这个引用变量名,如果不存在就在常量池中创建一个"小明"对象,然后把池中"小明"这个对象的引用地址返回给s1这个引用变量名,这样s1就会指向池中的"小明"对象。
采用new关键字创建对象时(比如String s1=“小明”;),JVM会先在常量池中查找是否有"小明"这个对象,如果有,就不在池中创建"小明"这个对象,直接在堆中创建一个"小明"对象,然后将堆中的"小明"对象的地址返回给s1这个引用变量名。如果常量池中没有"小明"这个对象,会首先在常量池中创建一个"小明"对象,然后再在堆中创建一个"小明"对象,之后将堆中的"小明"对象的地址返回给s1这个引用变量名。
4.equals和==的区别
1)equals和==本质上是没有任何区别的。
这是Object中的equals方法:
//Object中的equals方
public boolean equals(Object obj) {
return (this == obj);
}
Object中的equals方法实质上就是==作比较。引用类型String,Integer等类只是把Object类中的equals方法进行了重写,例如以下是Integer类重写的equals方法:
//Integer类重写的equals方法
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
如果我们创建的类不重写equals,就会直接调用Object方法的equals方法,本质上还是==,如下:
@Override
public boolean equals(Object obj) {
return super.equals(obj);
}
整理借鉴了很多大佬写的,在此无法一一说明,这只是个人用来查漏补缺的文章,如果对你有帮助我很高兴。