String字符串在内存中的分配:
jvm虚拟内存分布:
程序计数器是jvm执行程序的流水线,存放一些跳转指令,这个太高深,小白不懂。
本地方法栈是jvm调用操作系统方法所使用的栈。
虚拟机栈是jvm执行java代码所使用的栈。
方法区存放了一些常量、静态变量、类信息等,可以理解成class文件在内存中的存放位置。
虚拟机堆是jvm执行java代码所使用的堆。
Java中的常量池,实际上分为两种形态:静态常量池和运行时常量池。
所谓静态常量池,即*.class文件中的常量池,class文件中的常量池不仅仅包含字符串(数字)字面量,还包含类、方法的信息,占用class文件绝大部分空间。而运行时常量池,则是jvm虚拟机在完成类装载操作后,将class文件中的常量池载入到内存中,并保存在方法区中,我们常说的常量池,就是指方法区中的运行时常量池。
1.s1 == s4当然不相等,s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,是一个不可预料的部分,编译器不会优化,必须等到运行时才可以确定结果,结合字符串不变定理,鬼知道s4被分配到哪去了,所以地址肯定不同。
2.s1 == s9也不相等,道理差不多,虽然s7、s8在赋值的时候使用的字符串字面量,但是拼接成s9的时候,s7、s8作为两个变量,都是不可预料的,编译器毕竟是编译器,不可能当解释器用,所以不做优化,等到运行时,s7、s8拼接成的新字符串,在堆中地址不确定,不可能与方法区常量池中的s1地址相同。
3. s1 == s6这两个相等完全归功于intern方法,s5在堆中,内容为Hello ,intern方法会尝试将Hello字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了Hello字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。
至此,我们可以得出三个非常重要的结论:
必须要关注编译期的行为,才能更好的理解常量池。
运行时常量池中的常量,基本来源于各个class文件中的常量池。
程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。
以上所讲仅涉及字符串常量池,实际上还有整型常量池、浮点型常量池等等,但都大同小异,只不过数值类型的常量池不可以手动添加常量,程序启动时常量池中的常量就已经确定了,比如整型常量池中的常量围:-128~127,只有这个范围的数字可以用到常量池。
关于Integer在内存中的分配:
public class TestInteger { public static void main(String[] args) { Integer i1 = 40; Integer i2 = 40; Integer i3 = 0; Integer i4 = i1+i3; Integer i5 = new Integer(40); Integer i6 = new Integer(40); Integer i7 = new Integer(0); Integer i8 = new Integer(40)+new Integer(0); System.out.println("i1--i2\t"+(i1==i2));//true System.out.println("i1--i4\t"+(i1==i4));//true System.out.println("i1--i5\t"+(i1==i5));//false System.out.println("i5--i6\t"+(i5==i6));//false System.out.println("i5--i8\t"+(i5==i8));//false } } |
最后通过网上搜索得知Java为了提高性能提供了和String类一样的对象池机制,当然Java的八种基本类型的包装类(Packaging Type)也有对象池机制。
Integer i1=40;Java在编译的时候会执行将代码封装成Integer i1=Integer.valueOf(40);通过查看Source Code发现:
Integer.valueOf()中有个内部类IntegerCache(类似于一个常量数组,也叫对象池),它维护了一个Integer数组cache,长度为(128+127+1)=256,Integer类中还有一个Static Block(静态块)。
从这个静态块可以看出,Integer已经默认创建了数值【-128-127】的Integer缓存数据。所以使用Integer i1=40时,JVM会直接在该在对象池找到该值的引用。也就是说这种方式声明一个Integer对象时,JVM首先会在Integer对象的缓存池中查找有木有值为40的对象,如果有直接返回该对象的引用;如果没有,则使用New keyword创建一个对象,并返回该对象的引用地址。因为Java中【==】比较的是两个对象是否是同一个引用(即比较内存地址),i2和i2都是引用的同一个对象,So i1==i2结果为”true“;而使用new方式创建的i4=new Integer(40)、i5=new Integer(40),虽然他们的值相等,但是每次都会重新Create新的Integer对象,不会被放入到对象池中,所以他们不是同一个引用,输出false。
对于i1==i2+i3、i1==i4结果为True,是因为,Java的数学计算是在内存栈里操作的,Java会对i5、i6进行拆箱操作,其实比较的是基本类型(40=40+0),他们的值相同,因此结果为True。
Integer i9 = 400; Integer ii = 400; System.out.println("i9--ii\t"+(i9==ii));//false |
但是当值大于数组的范围时,
常量池内容回顾与小结:
常量池分为静态常量池和动态常量池。
静态常量池占据了字节码文件的大部分空间,而方法区就可以理解为存放字节码文件的地方。
动态常量池则是,在JVM虚拟机完成对类的加载后,将字节码文件载入内存,并存入方法区中。
== 是比较数据所在内存地址是否一样,.equals()方法是比较内容是否一致。
1.String的常量和拼接的常量是同样的地址,因为数据共享。
2.只要是new String出来的对象字符串就和任何常量和对象的地址不一样。
3.String s = s1 +"3";和"123"的常量或者拼接的地址都是不一样的。
而Integer类型就比较厉害了:
在常量池中有一个默认的存放int类型的数组,长度为(128+127+1)个,
只要在该范围内就是常量,数据共享,指向同一个地址,反之,超出这个范围
就会重新创建开辟新的空间,即:
Integer i1 = 40;Integer i2 = 40;sysout(i1==i2);//true
Integer i3 = 400; Integer i4 = 400;sysout(i3==i4)//false
而且:在数组范围内的变量的拼接都是自动编译成一个一个常量:
Integer i5 =30;Integer i6 = 10;Integer i7 = i5+i6; sysout(i1==i7);//true