1. Class文件常量池
class文件是一组二进制字节流,class文件常量池在编译阶段就已经确定。class文件常量池主要存放两大常量:字面量和符号引用。
class JavaBean{private int value = 1;public String s = "abc";public final static int f = 0x101;public void setValue(int v){final int temp = 3;this.value = temp + v;}public int getValue(){return value;}}
通过 javac 命令编译之后,用 javap -v 命令查看编译后的文件:
class JavaBasicKnowledge.JavaBeanminor version: 0major version: 52flags: ACC_SUPERConstant pool:#1 = Methodref #6.#29 // java/lang/Object."<init>":()V#2 = Fieldref #5.#30 // JavaBasicKnowledge/JavaBean.value:I#3 = String #31 // abc#4 = Fieldref #5.#32 // JavaBasicKnowledge/JavaBean.s:Ljava/lang/String;#5 = Class #33 // JavaBasicKnowledge/JavaBean#6 = Class #34 // java/lang/Object#7 = Utf8 value#8 = Utf8 I#9 = Utf8 s#10 = Utf8 Ljava/lang/String;#11 = Utf8 f#12 = Utf8 ConstantValue#13 = Integer 257#14 = Utf8 <init>#15 = Utf8 ()V#16 = Utf8 Code#17 = Utf8 LineNumberTable#18 = Utf8 LocalVariableTable#19 = Utf8 this#20 = Utf8 LJavaBasicKnowledge/JavaBean;#21 = Utf8 setValue#22 = Utf8 (I)V#23 = Utf8 v#24 = Utf8 temp#25 = Utf8 getValue#26 = Utf8 ()I#27 = Utf8 SourceFile#28 = Utf8 StringConstantPool.java#29 = NameAndType #14:#15 // "<init>":()V#30 = NameAndType #7:#8 // value:I#31 = Utf8 abc#32 = NameAndType #9:#10 // s:Ljava/lang/String;#33 = Utf8 JavaBasicKnowledge/JavaBean#34 = Utf8 java/lang/Object
1.1. 字面量
字面量接近 java 语言层面的常量概念。
【字符串】
字符串的值。
// public String s = "abc"; 中的 "abc"。#9 = Utf8 s#3 = String #31#31 = Utf8 abc
【final 修饰变量】
包括成员变量、静态变量、实例变量和局部变量。
// public final static int f = 0x101; 中的0x101。#11 = Utf8 f#12 = Utf8 ConstantValue#13 = Integer 257
【注意】
- 字面量指的是数据的值,class文件常量池中一般指字符串的值和常量的值。
- 对于基本类型数据和局部变量,他们的字面量不会存在于class文件常量池。
// private int value = 1; 常量池中只保留了字段描述符 I 和字段名称 value#7 = Utf8 value#8 = Utf8 I
1.2. 符号引用
符号引用主要设涉及编译原理方面的概念。
【类和接口的全限定名】
将类名中原来的 “.” 替换为 “/“ 得到的,主要用于在运行时解析得到类的直接引用。
#5 = Class #33#33 = Utf8 JavaBasicKnowledge/JavaBean
【字段名称和描述符】
类或者接口中声明的变量名称和类型,包括类变量和实例变量。
#4 = Fieldref #5.#32#5 = Class #33#32 = NameAndType #7:#8// 基本数据类型的变量,常量池只保留字段名称和字段描述符// private int value = 1;#7 = Utf8 value // 变量名#8 = Utf8 I // 变量类型// 形参变量和局部变量,常量池只保留字段名称// public void setValue(int v) {}#23 = Utf8 v // 形参变量名// final int temp = 3;#24 = Utf8 temp // 局部变量名
【方法名称和描述符】
方法的名称和参数类型、返回值类型
// public void setValue(int v) {}#21 = Utf8 setValue // 方法名#22 = Utf8 (I)V // 参数类型 I 返回值类型 V#25 = Utf8 getValue // 方法名#26 = Utf8 ()I // 参数类型空 返回值类型 I
2. 运行时常量池
运行时常量池是方法区的一部分,不同的类共用一个运行时常量池。
- Class 加载过程,class 文件常量池会进入运行时常量池。
- 多个 class 文件常量池中相同的字符串在运行时常量池只会存在一份。
- class 文件常量池中的符号引用,会在 Class 的解析阶段翻译成直接引用,一起存储在运行时常量池中。
- 运行时常量池具有动态性,其中的内容并不全部来自 class 常量池,在运行时可以通过代码生成常量并将其放入,这种特性被用的最多的就是
String.intern()。
3. 字符串常量池
字符串常量池和运行时常量池不是一个概念。
- JDK 1.7 以前字符串常量池和运行时常量池都在方法区中。
- JDK 1.7 及以后运行时常量池在方法区中,字符串常量池在堆中。
3.1. 创建字符串对象的方式
3.1.1. 字面量创建
String s = "abc";
- JVM 先去字符串常量池中查找
equals("abc")的对象。 - 如果有则不用创建,直接将字符串常量池中的
"abc"对象的引用赋值给s。 - 如果没有则在堆中创建一个新的
"abc"对象,并将堆中的"abc"对象的引用驻留一份在字符串常量池中,再将字符串常量池中的"abc"对象的引用赋值给s。
s => 字符串常量池中(同时也是堆中)的 “abc” 对象。且无论创建多少次,只要字符串的值相同,它们都指向堆中的同一个对象。
3.1.2. 构造方法创建
String s = new String("abc");
- JVM 先去字符串常量池中查找
equals("abc")的对象。 - 如果有则在堆中创建一个新的
"abc"对象。 - 如果没有则在堆和字符串常量池中各创建一个新的
"abc"对象。 - 将字符串常量池中的
"abc"对象的引用赋值给s。
s => 堆中的 “abc” 对象。
String s1 = "Hello";String s2 = "Hello";String s3 = "Hel" + "lo";String s4 = "Hel" + new String("lo");String s5 = new String("Hello");String s6 = new String("Hello");String s7 = "H";String s8 = "ello";String s9 = s7 + s8;// true。 s1和s2都指向字符串常量池中的同一个"Hello"对象System.out.println(s1 == s2);// true。 s3会在编译时合并成"Hello"对象,因此s1和s3都指向字符串常量池中的同一个"Hello"对象System.out.println(s1 == s3);// false。s4只会创建"Hel"对象,"lo"对象会在堆中创建,因此s1和s4指向的对象不同System.out.println(s1 == s4);// false。s1指向字符串常量池中的"Hello"对象,s5指向堆中的"Hello"对象System.out.println(s1 == s5);// true。 s1指向字符串常量池中的"Hello"对象,s5.intern()会返回字符串常量池中的"Hello"对象System.out.println(s1 == s5.intern());// false。s9会在运行时动态赋值,因此s1和s9指向的对象不同System.out.println(s1 == s9);// false。s5和s6指向堆中不同的"Hello"对象System.out.println(s5 == s6);// true。 s5.intern()和s6.intern()都会返回字符串常量池中的同一个"Hello"对象System.out.println(s5.intern() == s6.intern());
3.2. intern 方法
将对应的字面量进行特殊处理。
- JDK 1.6及以前:检查字面量对象是否在字符串常量池中,如果存在则直接返回字符串常量池的对象,如果不存在,则在字符串常量池创建并返回该字面量对象。
- JDK 1.7及以后:检查字面量对象是否在字符串常量池中,如果存在则直接返回字符串常量池的对象,如果不存在,则把堆中的字面量对象的引用添加到字符串常量池中,并返回该字面量对象。
4. 包装类对象常量池
java 基本类型的包装类 Byte,Short,Integer,Long,Character,Boolean 实现了常量池技术,而 Float 和 Double 则没有实现。
另外上面这 6 种整型的包装类也只是在对应值在 [-128,127] 区间时才使用缓存数据, 超出此范围仍然会去创建新的对象。
Integer i1 = 127;Integer i2 = 127;System.out.println(i1 == i2); // true。 s1和s2都指向常量池中的同一个对象i2 = 200;System.out.println(i1 == i2); // false。s2重新创建了一个新的对象,s1和s2指向不同对象Integer i3 = 128;Integer i4 = 128;System.out.println(i3 == i4); // false。128超出范围,s1和s2都指向不同对象Integer i5 = -128;Integer i6 = -128;System.out.println(i5 == i6); // true。 s1和s2都指向常量池中的同一个对象Boolean bool1 = false;Boolean bool2 = false;System.out.println(bool1 == bool2); // true。 s1和s2都指向常量池中的同一个对象Double d1 = 1.0;Double d2 = 1.0;System.out.println(d1 == d2); // false。Double类没有使用常量池。s1和s2指向不同对象
