JVM:运行时方法区之常量池
Java JVM About 7,622 wordsJava 代码
public class StringTableDemo {
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2;
String s5 = "a" + "b";
System.out.println(s3 == s4);
System.out.println(s3.equals(s4));
System.out.println(s3 == s5);
}
}
Class 字节码
javap -v .\StringTableDemo.class
字节码文件(注释了输出语句后编译的结果)
Classfile /D:/jvm/StringTableDemo.class
Last modified Dec 17, 2021; size 712 bytes
MD5 checksum 7c317c34bf6b2d10ebe7897c17224805
Compiled from "StringTableDemo.java"
public class jvm.StringTableDemo
minor version: 0
major version: 52
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #9 // jvm/StringTableDemo
super_class: #10 // java/lang/Object
interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
#1 = Methodref #10.#30 // java/lang/Object."<init>":()V
#2 = String #31 // a
#3 = String #32 // b
#4 = String #33 // ab
#5 = Class #34 // java/lang/StringBuilder
#6 = Methodref #5.#30 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#35 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Class #37 // jvm/StringTableDemo
#10 = Class #38 // java/lang/Object
#11 = Utf8 <init>
#12 = Utf8 ()V
#13 = Utf8 Code
#14 = Utf8 LineNumberTable
#15 = Utf8 LocalVariableTable
#16 = Utf8 this
#17 = Utf8 Ljvm/StringTableDemo;
#18 = Utf8 main
#19 = Utf8 ([Ljava/lang/String;)V
#20 = Utf8 args
#21 = Utf8 [Ljava/lang/String;
#22 = Utf8 s1
#23 = Utf8 Ljava/lang/String;
#24 = Utf8 s2
#25 = Utf8 s3
#26 = Utf8 s4
#27 = Utf8 s5
#28 = Utf8 SourceFile
#29 = Utf8 StringTableDemo.java
#30 = NameAndType #11:#12 // "<init>":()V
#31 = Utf8 a
#32 = Utf8 b
#33 = Utf8 ab
#34 = Utf8 java/lang/StringBuilder
#35 = NameAndType #39:#40 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#36 = NameAndType #41:#42 // toString:()Ljava/lang/String;
#37 = Utf8 jvm/StringTableDemo
#38 = Utf8 java/lang/Object
#39 = Utf8 append
#40 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder;
#41 = Utf8 toString
#42 = Utf8 ()Ljava/lang/String;
{
public jvm.StringTableDemo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Ljvm/StringTableDemo;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: (0x0009) ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=6, args_size=1
0: ldc #2 // String a
2: astore_1
3: ldc #3 // String b
5: astore_2
6: ldc #4 // String ab
8: astore_3
9: new #5 // class java/lang/StringBuilder
12: dup
13: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V
16: aload_1
17: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
20: aload_2
21: invokevirtual #7 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: invokevirtual #8 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
27: astore 4
29: ldc #4 // String ab
31: astore 5
33: return
LineNumberTable:
line 31: 0
line 32: 3
line 33: 6
line 34: 9
line 35: 29
line 40: 33
LocalVariableTable:
Start Length Slot Name Signature
0 34 0 args [Ljava/lang/String;
3 31 1 s1 Ljava/lang/String;
6 28 2 s2 Ljava/lang/String;
9 25 3 s3 Ljava/lang/String;
29 5 4 s4 Ljava/lang/String;
33 1 5 s5 Ljava/lang/String;
}
SourceFile: "StringTableDemo.java"
常量池
常量池在每个字节码文件的Constant pool
中。
虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量信息等。
Constant pool:
#1 = Methodref #10.#30 // java/lang/Object."<init>":()V
#2 = String #31 // a
#3 = String #32 // b
#4 = String #33 // ab
#5 = Class #34 // java/lang/StringBuilder
#6 = Methodref #5.#30 // java/lang/StringBuilder."<init>":()V
#7 = Methodref #5.#35 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#8 = Methodref #5.#36 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#9 = Class #37 // jvm/StringTableDemo
#10 = Class #38 // java/lang/Object
...
运行时常量池
当类被加载,它的常量池信息就会被放入到运行时常量池,并把里面的符号地址变为真实内存地址。
字符串常量池
常量池中的信息,都会被加载到运行时常量池中,这时a
、b
、ab
都是常量池中的符号,还没有变为Java
字符串对象。
ldc #2
会把a
符号变为a
字符串对象。ldc #3
会把b
符号变为b
字符串对象。ldc #4
会把ab
符号变为ab
字符串对象。
StringTable ["a", "b", "ab"]
:hashtable
结构,不能扩容。
StringBuilder
最后toString
中new
的String
用的是char
数组,所以不会在字符串常量池中加入字符串。
public String(char value[], int offset, int count)
public static void main(String[] args) {
String s1 = "a";
String s2 = "b";
String s3 = "ab";
String s4 = s1 + s2; // new StringBuffer().append("a").append("b").toString() // new String("ab")
String s5 = "a" + "b"; // javac 在编译期间的优化,结果已经在编译期已经确定为 ab
// System.out.println(s3 == s4); // false s3 指向字符串常量池中的 ab,s4 指向堆中的 new String("ab") 对象
// System.out.println(s3.equals(s4)); // true
// System.out.println(s3 == s5); // true
}
StringTable
特性
- 常量池中的字符串仅仅是符号,第一次用到时才变为对象(延迟加载)。
- 利用字符串常量池的机制,来避免重复创建字符串对象。
- 字符串变量拼接的原理是
StringBuilder
(Java8
)。 - 字符串常量拼接的原理是编译期优化。
- 可以使用
intern
方法,主动将字符串常量池还没有的字符串对象放入字符串常量池中。
intern
s1 == s2
:false
。xy
先放入字符串常量池中,s1
执行常量池中的xy
;s2
指向堆内存中的StringBuilder
生成的String
,再调用intern
方法发现字符串常量池中已经有xy
了,不再放入,引用也不发生改变继续指向堆内存中。所以不相等。
public static void main(String[] args) {
String s1 = "xy";
String s2 = new String("x") + new String("y");
s2.intern();
System.out.println(s1 == s2);
}
s1 == s2
:true
。s2
指向堆内存中的StringBuilder
生成的String
,再调用intern
方法发现字符串常量池中没有xy
,放入字符串常量池中,且将引用指向字符串常量池中的xy
;而s1
指向xy
发现字符串常量池已经有xy
了,直接指向字符串常量池中的xy
,所以s1
和s2
都指向字符串常量池中的xy
,所以相等。
public static void main(String[] args) {
String s2 = new String("x") + new String("y");
s2.intern();
String s1 = "xy";
System.out.println(s1 == s2);
}
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓