深入浅出java常量池和基本数据类型对象池
目录
字节码常量池和运行时常量池
字节码常量池也就是静态常量池,它可以看作是一种静态资源,存放在class字节码中,用于存放编译期生成的各种字面量(Literal)和符号引用(Symbolic References)。我们一般可以通过javap命令生成更可读的JVM字节码文件:javap -v xxx.class
public class com.cyb.sas.entity.Client extends com.baomidou.mybatisplus.extension.activerecord.Model<com.cyb.sas.entity.Client>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool: //常量池
#1 = Methodref #27.#91 // com/baomidou/mybatisplus/extension/activerecord/Model."<init>":()V
#2 = Fieldref #7.#92 // com/cyb/sas/entity/Client.id:I
#3 = Fieldref #7.#93 // com/cyb/sas/entity/Client.sname:Ljava/lang/String;
#4 = Fieldref #7.#94 // com/cyb/sas/entity/Client.scall:Ljava/lang/String;
#5 = Fieldref #7.#95 // com/cyb/sas/entity/Client.address:Ljava/lang/String;
#6 = Fieldref #7.#96 // com/cyb/sas/entity/Client.mark:Ljava/lang/String;
#7 = Class #97 // com/cyb/sas/entity/Client
#8 = Methodref #7.#98 // com/cyb/sas/entity/Client.canEqual:(Ljava/lang/Object;)Z
#9 = Methodref #7.#99 // com/cyb/sas/entity/Client.getId:()I
#10 = Methodref #7.#100 // com/cyb/sas/entity/Client.getSname:()Ljava/lang/String;
#11 = Methodref #101.#102 // java/lang/Object.equals:(Ljava/lang/Object;)Z
#12 = Methodref #7.#103 // com/cyb/sas/entity/Client.getScall:()Ljava/lang/String;
#13 = Methodref #7.#104 // com/cyb/sas/entity/Client.getAddress:()Ljava/lang/String;
#14 = Methodref #7.#105 // com/cyb/sas/entity/Client.getMark:()Ljava/lang/String;
#15 = Methodref #101.#106 // java/lang/Object.hashCode:()I
#16 = Class #107 // java/lang/StringBuilder
#17 = Methodref #16.#91 // java/lang/StringBuilder."<init>":()V
#18 = String #108 // Client(id=
#19 = Methodref #16.#109 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#20 = Methodref #16.#110 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#21 = String #111 // , sname=
#22 = String #112 // , scall=
#23 = String #113 // , address=
#24 = String #114 // , mark=
#25 = String #115 // )
#26 = Methodref #16.#116 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#27 = Class #117 // com/baomidou/mybatisplus/extension/activerecord/Model
#28 = Utf8 id
#29 = Utf8 I
#30 = Utf8 RuntimeVisibleAnnotations
#31 = Utf8 Lcom/baomidou/mybatisplus/annotation/TableId;
#32 = Utf8 type
#33 = Utf8 Lcom/baomidou/mybatisplus/annotation/IdType;
#34 = Utf8 AUTO
#35 = Utf8 sname
#36 = Utf8 Ljava/lang/String;
字面量就是指由字母、数字等构成的字符串或者数值常量 字面量只可以右值出现,所谓右值是指等号右边的值,如:String x="1" 这里的x为左值,"1" 为右值。在这个例子中"1" 就是字面量。
符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
如String x="1"中x就是符号引用,这些常量池现在是静态信息,只有到运行时被加载到内存后,这些符号才有对应的内存地址信息,这些常量池一旦被装 入内存就变成运行时常量池,对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用,也就是动态链接了。例如,getId:()I这个符号引用在运行时就会被转变为getId()方法具体代码在内存中的地址,主要通过对象头里的类型指针去转换直接引用。
字符串常量池
字符串常量池大家应该很熟悉,String x="1" 中"1"就会当作一个字符串常量存入字符串常量池,值得注意的是,在jdk1.6中,字符串常量池存放在永久代,从jdk1.7开始字符串常量池被移到了堆区,Jdk1.8及之后无永久代,运行时常量池在元空间,字符串常量池里依然在堆里。
字符串常量池底层是hotspot的C++实现的,底层类似一个HashTable, 保存的本质上是字符串对象的引用。 看一道比较常见的面试题,下面的代码创建了多少个 String 对象?(以下仅讨论jdk1.7以上版本)
String s1 = new String("a") + new String("b");
String s2 = s1.intern();
System.out.println(s1 == s2);
// 在 JDK 1.7 及以上的版本输出是 true,创建了 5 个对象
下面来看为什么, new String("a")类似这种代码,会在字符串常量池创建一个常量"a",同时会在堆区创建一个字符串对象"a",所以这行代码会创建两个对象,而s1.intern()这句会调用一个native方法,如果常量池已经包含一个等于此对象的字符串 (用equals(oject)方法确定),则返回池中的字符串。否则,将intern将返回指向当前字符串 s1的引用(jdk1.6版本需要将 s1 复制到字符串常量池里)。所以上面的代码创建了一个"a"常量,一个"b"常量,一个"a"对象,一个"b"对象,一个"ab"对象。
再来看一个例子:以下代码输入结果是什么?
String s1 = "ab";
String s2 = "b";
String s3 = "a" + s2;
System.out.println(s1 == s3);
答案是false,why?JVM对于字符串引用,由于在字符串的"+"连接中,有字符串引用存在,而引用的值在程序编译期是无法确定的,只有在程序运行期来动态分配并将连接后的新地址赋给s3。所以上面程序的结果也就为 false。请思考,如果把String s3 = "a" + s2;这句改为String s3 = "a" + "b";呢,答案是true,因为"a" + "b"会被编译器优化为"ab"。s1自然就和s3相等了。
基本数据类型的对象池
java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是 Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外 Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池。因为一般这种比较小的数用到的概率相对较小。
Integer i1 = 127; //这种调用底层实际是执行的Integer.valueOf(127),里面用到了IntegerCache对象池
Integer i2 = 127;
System.out.println(i1 == i2);//输出true
//值大于127时,不会从对象池中取对象
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);//输出false
下面来看源码:
static final int low = -128; static final int high;...
int h = 127;...
cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++);
可以看到Integer类中维护了一个Integer数组,长度为256,存放了-128~127的整型值。查看其它基本类型的源码可知,同样内部都维护了一个-128~127的缓存数组(Character中的数组为0~127),当超出缓存数组范围时,会用new对象的形式来新创建一个基本类型的包装类对象。