Java运行时常量池

概述

        1. 常量池在内存中的位置:        

  • 在JDK1.6中,方法区是以永久代的方式实现(HotSpot),常量池是方法区的一部分。
  • 在JDK1.7中,方法区合并到堆内存中,常量池可以说在堆内存中。
  • 在JDK8中,方法区又被剥离出来,只不过实现方式不是永久代,此时的方法区叫元数据区,常量池也就在元数据区。

        2. 常量池的概念以及作用

  • 通常来讲,所有变量,包括基本类型和引用类型,他们都存在虚拟机栈中,包括变量类型、变量名称、和变量值。对于基本类型来说,值就是具体的值;而对于引用类型来说,值是指对象实例在堆内存中对应的地址
  • 对于引用类型的数据,如果没有常量池,那么就会反复在堆内存中创建和销毁值相同的对象,这样有损系统性能。相比之下,基本类型的变量就不会有这样的弊端,所以一般不会放到常量池中,直接在栈中操作即可。
  • 常量池的作用是避免频繁地创建和销毁值相同的对象,节省内存空间,节省运行时间。比如,需要已存在的字符串,直接从常量池中取即可,无需重新创建。
  • 常量池:五大基本类型对应的包装类的常量池、String字符串常量池。

基本类型包装类常量池

        首先,基本数据类型不会用到常量池,因为一般的运算、判断和赋值都是对值的操作,直接在虚拟机栈中进行,不涉及到对象,也就与堆内存无关(也就涉及不到对象的创建以及垃圾回收),不会对系统性能造成太大的影响。 

  • Java中的Byte,Short,Integer,Long,Character,Boolean五种包装类实现了常量池技术。只有数值处在[-128,127]这个范围的包装类对象能存到常量池当中,超过这个范围的对象都要在堆内存中创建。利用new创建的包装类对象仍然存在堆内存中。
// 在[-128,127]这个范围 
Integer i1 = 40;
Integer i2 = 40;
System.out.println(i1==i2);//输出TRUE

// 不在上述范围
Integer i3 = 400;
Integer i4 = 400;
System.out.println(i3==i4); // false
  • 两种浮点数类型的包装类Float、Double不支持常量池技术。
Double i1=1.2;
Double i2=1.2;
System.out.println(i1==i2);//输出false

  • 包装类对象不支持算术运算(+、-、*、/、%)和比较运算(==,!=),包装类对象在进行上述运算时会自动拆箱成基本数据类型,不再是包装类对象。
  Integer i1 = 40;
  Integer i2 = 40;
  Integer i3 = 0;
  Integer i4 = new Integer(40);
  Integer i5 = new Integer(40);
  Integer i6 = new Integer(0);
  
  System.out.println("i1=i2   " + (i1 == i2));    // true
  System.out.println("i1=i2+i3   " + (i1 == i2 + i3));    // true
  System.out.println("i1=i4   " + (i1 == i4));    // false
  System.out.println("i4=i5   " + (i4 == i5));    // false
  // i4,i5,i6三个对象都自动拆箱为基本数据类型,不再是对象,而是数值之间的比较。
  System.out.println("i4=i5+i6   " + (i4 == i5 + i6));    // true
  System.out.println("40=i5+i6   " + (40 == i5 + i6));    // true 

字符串常量池

  • String str1 = "a"; 用引号直接初始化的字符串str1属于字符串常量编译期就已经放入到常量池中,str1是一个引用,str1的值是“a”在常量池中的地址
  • String str2 = new String("a"); 此时的str2是字符串变量,其指向的对象实例在堆内存中;指向的字符串对象也可以。类加载和运行时都会处理上述语句。
    • 类加载时,先判断常量池中是否已经有“a”,如果有,则什么都不做;如果没有,则创建一个“a”放在常量池中。
    • 运行时,执行到new这个语句,是将常量池中的实例复制一份放到堆内存中,也无需重新创建,但str2的值是实例在堆内存中的地址。虽然不是创建了两个对象,但是确实是新增了两个新的对象
    • 上述涉及到类加载的过程未必准确,但结果相同,变量指向的是堆内存中的对象。
  • 出现字符串连接符“+”的两种情况:

        1. 只有包含双引号" "指定的字符串 (都在pool中)

// 在编译期间就能确定"aa"这样一个字符串对象,所以是放到常量池中,但常量池中没有"a"
String str1 = "a" + "a";

        2. 不全是双引号" "指定的字符串进行连接(不都在pool中)

String str1 = new String("b");
String str2 = "c" + str1;    // 在编译期间不能确定,运行期间存到堆内存中

String str3 = new String("c");
String str4 = str1 + str3;    // 在编译期间不能确定,运行期间存到堆内存中
System.out.println(str2==str4)    // false    都在堆内存中,地址肯定不同

示例:final修饰的变量编译器已确定,对象存在常量池中

String s0 = "111";              //pool
String s1 = new String("111");  //heap
final String s2 = "111";        //pool    编译器已确定
String s3 = "sss111";           //pool
String s4 = "sss" + "111";      //pool
String s5 = "sss" + s0;         //heap 
String s6 = "sss" + s1;         //heap
String s7 = "sss" + s2;         //pool
String s8 = "sss" + s0;         //heap
 
System.out.println(s3 == s4);   //true
System.out.println(s3 == s5);   //false
System.out.println(s3 == s6);   //false
System.out.println(s3 == s7);   //true
System.out.println(s5 == s6);   //false
System.out.println(s5 == s8);   //false

判断原则(指向常量池还是堆内存)

1. 编译期间可以创建的对象放在常量池中。

String str1 = "xyz";
Integer a = 1;

2. 运行期间用new创建的对象放在堆内存中。

String str1 = new String("aaa");
Integer a = new Integer(2);

3. 存放位置不同的两个对象对应的引用一定不相等。

String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); // false

4. 运行时常量池具有动态性,即运行期间也可以向常量池中添加新的常量。

  • 被开发人员利用最多的是String类的intern()方法。
// 动态性
// 常量池中本没有“abc”,程序执行以下语句时,会先在常量池中创建一个“abc”对象
// 然后把对象的引用(地址)返回给str1。此时str1的值是对象引用。
String str1 = "abc".intern();


// 常量池中已经有“xyz”对象了,那么无需创建,直接将对象返回给str3,此时str3的值是对象,而非引用。
String str2 = "xyz";
String str3 = str2.intern();
  • 其次通过new创建的对象,也都会在类加载时先创建一份保存到常量池中,运行期间遇到的new相同字符串的语句时,都是直接从常量池中复制一份到堆内存中,这样也可以在一定程度上减轻创建相同内容的对象的消耗。

5. 如果指向的对象都在常量池,那么指向的一定是同一个对象,即str1=str2 (值相同,地址相同)

String str1 = "abc";
String str2 = "abc";
System.out.println(str1==str2); // true

6. 除了5以外的两种情况,两个对象引用的值都不相等。

// 情况1:一个在常量池中,一个在堆内存中
String str1 = "abc";
String str2 = new String("abc");
System.out.println(str1==str2); // false

// 情况2:两个都在堆内存中
String str3 = new String("xyz");
String str4 = new String("xyz");
System.out.println(str3==str4); //false

参考链接

  1. Java常量池理解与总结 - 简书 (jianshu.com)
  2. 专题整理之—String的字符串常量池 - 简书 (jianshu.com)

你可能感兴趣的:(深入理解java虚拟机,java)