一.Class 文件常量池
1.class 文件中存在常量池,其在编译阶段就已经确定了。
2.用javap -v 命令查看编译后的文件:javap -v ClassConstantPool.class
3.class文件常量池主要存放两大常量:字面量和符号引用
1.查看class字节码的具体方法
先创建一个这样的类
public class ClassConstantPool {
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 下找到编译后的这个类的上一级目录,并点击Terminal
随后,输入命令 javap -v ClassConstantPool.class 就可以了。
public class ClassConstantPool
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // ClassConstantPool
super_class: #6 // java/lang/Object
interfaces: 0, fields: 3, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #6.#29 // java/lang/Object."<init>":()V
#2 = Fieldref #5.#30 // ClassConstantPool.value:I
#3 = String #31 // abc
#4 = Fieldref #5.#32 // ClassConstantPool.s:Ljava/lang/String;
#5 = Class #33 // ClassConstantPool
#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 LClassConstantPool;
#21 = Utf8 setValue
#22 = Utf8 (I)V
#23 = Utf8 v
#24 = Utf8 temp
#25 = Utf8 getValue
#26 = Utf8 ()I
#27 = Utf8 SourceFile
#28 = Utf8 ClassConstantPool.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 ClassConstantPool
#34 = Utf8 java/lang/Object
{
public java.lang.String s;
descriptor: Ljava/lang/String;
flags: (0x0001) ACC_PUBLIC
public static final int f;
descriptor: I
flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 257
public ClassConstantPool();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #2 // Field value:I
9: aload_0
10: ldc #3 // String abc
12: putfield #4 // Field s:Ljava/lang/String;
15: return
LineNumberTable:
line 4: 0
line 5: 4
line 6: 9
LocalVariableTable:
Start Length Slot Name Signature
0 16 0 this LClassConstantPool;
public void setValue(int);
descriptor: (I)V
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=3, args_size=2
0: iconst_3
1: istore_2
2: aload_0
3: iconst_3
4: iload_1
5: iadd
6: putfield #2 // Field value:I
9: return
LineNumberTable:
line 9: 0
line 10: 2
line 11: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this LClassConstantPool;
0 10 1 v I
2 8 2 temp I
public int getValue();
descriptor: ()I
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field value:I
4: ireturn
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this LClassConstantPool;
}
SourceFile: "ClassConstantPool.java"
这就是反编后的字节码。
字节码解释地址:https://cloud.tencent.com/developer/article/1333540
2.字面量
字面量主要包括:
文本字符串,也就是我们经常声明的:public String s = "abc";中的"abc"
#9 = Utf8 s
#3 = String #31 // abc
#31 = Utf8 abc
用final修饰的成员变量,包括静态变量、实例变量和局部变量
#11 = Utf8 f
#12 = Utf8 ConstantValue
#13 = Integer 257
存在于常量池的字面量,指的是数据的值,也就是abc和0x101(257),通过对常量池的观察可知这两个字 面量是确实存在于常量池中。
而对于基本类型数据(甚至是方法中的局部变量),也就是上面的private int value = 1;常量池中只保留了 他的的字段描述符I和字段的名称value,他们的字面量不会存在于常量池。
3.符号引用
符号引用主要包括下面三类常量:
类和接口的全限定名,也就是Ljava/lang/String;这样,将类名中原来的"."替换为"/"得到的,主要用于在 运行时解析得到类的直接引用,像上面:
#5 = Class #33 // com/dongnaoedu/jvmstudylib/JavaBean
#33 = Utf8 JavaBasicKnowledge/JavaBean
字段的名称和描述符,字段也就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量
#4 = Fieldref #5.#32 // com/dongnaoedu/jvmstudylib/JavaBean.value:I
#5 = Class #33 // com/dongnaoedu/jvmstudylib/JavaBean
#32 = NameAndType #7:#8 // value:I
#7 = Utf8 value
#8 = Utf8 I
//这两个是局部变量,只保留字段名称
#23 = Utf8 v
#24 = Utf8 temp
方法的名称和描述符,方法的描述类似于JNI动态注册时的“方法签名”,也就是参数类型+返回值类型
#21 = Utf8 setValue
#22 = Utf8 (I)V
#25 = Utf8 getValue
#26 = Utf8 ()I
二.运行时常量池
所谓的运行时常量池其实就是将编译后的类信息放入运行时的一个区域中,用来动态获取类信息。 运行时 常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个 class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持 一致。
三.字符串常量池
字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符 串对象实例的引用值存到string pool中(string pool中存的是引用值而不是具体的实例对象,具体的实例 对象是在堆中开辟的一块空间存放的)。 在HotSpot VM里实现的string pool功能的是一个StringTable 类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留 字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻 留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。
String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // true
System.out.println(s1 == s4); // false
System.out.println(s1 == s9); // false
intern方法
如果字符串常量池里存在一个和当前字符串对象等价的字符串对象(equals==true认为相同),那么返回字符串常量池里那个对象.如果不存在,把当前字符串对象存进常量池, 返回当前字符串对象。
String test1 = "abc";
String test2 = new String("abc");
String str = test2.intern();
字符串引用str,如果直接用str=test2,那么就会如图中蓝线所示,指向不在常量池中的字符串对象
如果使用上面的str=test2.intern(),则会如同红线所示(因为常量池里的字符串对象和test2是等价的, intern会返回常量池里的那个
不在常量池中的字符串对象"str"在没有其他引用的情况下,可以进行回收,这是intern()真正的好处