java—浅谈常量池

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值