JVM学习笔记——内存结构篇( 六 )

StringTable串池简介我们首先来简单介绍一下串池:

  • 串池的本质是一个哈希表,其中的每个元素都是唯一的
我们在这里稍微解释一下为什么StringTable会移动到堆中:
  • jdk7中将StringTable放到了堆空间中
  • 因为永久代的回收效率很低,在full GC的时候才会触发 。
  • 而Full GC是老年代空间不足、永久代空间不足时才会触发 。这就导致StringTable回收效率不高 。
  • 而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足 。放到堆里,能及时回收内存 。
然后我们提前介绍一下串池的特点:
  • 常量池的字符串仅仅是符号,第一次用到时才会变为对象
  • 利用串池的机制,可以避免重复创建字符串对象
  • 字符串变量拼接的原理是StringBuiler拼接(jdk1.8)
  • 字符串常量拼接的原理是编译期优化
  • 可以使用intern方法,主动将串池中还没有的字符串放入串池
StringTable串池详细介绍我们通过一段代码来仔细介绍串池:
package cn.itcast.jvm.t1.stringtable;// StringTable [ "a", "b" ,"ab" ]hashtable 结构,不能扩容public class Demo1_22 {// 常量池中的信息,都会被加载到运行时常量池中,这时 a b ab 都是常量池中的符号,还没有变为 java 字符串对象public static void main(String[] args) {String s1 = "a";String s2 = "b";String s3 = "ab";String s4 = s1 + s2; // new StringBuilder().append("a").append("b").toString()new String("ab")String s5 = "a" + "b";// javac 在编译期间的优化,结果已经在编译期确定为abSystem.out.println(s3 == s5);}}然后我们对其进行编译解码:
// 解码语句javap -v Demo1_22.class// 基本信息Classfile /E:/编程内容/JVM/资料-解密JVM/代码/jvm/out/production/jvm/cn/itcast/jvm/t1/stringtable/Demo1_22.classLast modified 2022-11-2; size 985 bytesMD5 checksum a5eb84bf1a7d8a1e725491f36237777bCompiled from "Demo1_22.java"public class cn.itcast.jvm.t1.stringtable.Demo1_22minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER// 常量池Constant pool:#1 = Methodref#12.#36// java/lang/Object."<init>":()V#2 = String#37// a#3 = String#38// b#4 = String#39// ab#5 = Class#40// java/lang/StringBuilder#6 = Methodref#5.#36// java/lang/StringBuilder."<init>":()V#7 = Methodref#5.#41// java/lang/StringBuilder.append:#8 = Methodref#5.#42// java/lang/StringBuilder.toString:()Ljava/lang/String;#9 = Fieldref#43.#44// java/lang/System.out:Ljava/io/PrintStream;#10 = Methodref#45.#46// java/io/PrintStream.println:(Z)V#11 = Class#47// cn/itcast/jvm/t1/stringtable/Demo1_22#12 = Class#48// java/lang/Object#13 = Utf8<init>#14 = Utf8()V#15 = Utf8Code#16 = Utf8LineNumberTable#17 = Utf8LocalVariableTable#18 = Utf8this#19 = Utf8Lcn/itcast/jvm/t1/stringtable/Demo1_22;#20 = Utf8main#21 = Utf8([Ljava/lang/String;)V#22 = Utf8args#23 = Utf8[Ljava/lang/String;#24 = Utf8s1#25 = Utf8Ljava/lang/String;#26 = Utf8s2#27 = Utf8s3#28 = Utf8s4#29 = Utf8s5#30 = Utf8StackMapTable#31 = Class#23// "[Ljava/lang/String;"#32 = Class#49// java/lang/String#33 = Class#50// java/io/PrintStream#34 = Utf8SourceFile#35 = Utf8Demo1_22.java#36 = NameAndType#13:#14// "<init>":()V#37 = Utf8a#38 = Utf8b#39 = Utf8ab#40 = Utf8java/lang/StringBuilder#41 = NameAndType#51:#52// append:(Ljava/lang/String;)Ljava/lang/StringBuilder;#42 = NameAndType#53:#54// toString:()Ljava/lang/String;#43 = Class#55// java/lang/System#44 = NameAndType#56:#57// out:Ljava/io/PrintStream;#45 = Class#50// java/io/PrintStream#46 = NameAndType#58:#59// println:(Z)V#47 = Utf8cn/itcast/jvm/t1/stringtable/Demo1_22#48 = Utf8java/lang/Object#49 = Utf8java/lang/String#50 = Utf8java/io/PrintStream#51 = Utf8append#52 = Utf8(Ljava/lang/String;)Ljava/lang/StringBuilder;#53 = Utf8toString#54 = Utf8()Ljava/lang/String;#55 = Utf8java/lang/System#56 = Utf8out#57 = Utf8Ljava/io/PrintStream;#58 = Utf8println#59 = Utf8(Z)V// 代码解释{public cn.itcast.jvm.t1.stringtable.Demo1_22();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1// Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:StartLengthSlotNameSignature050thisLcn/itcast/jvm/t1/stringtable/Demo1_22;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=3, locals=6, args_size=1// 从这里开始我们正式进入main// 在最开始:StringTable=[],堆=[]// ldc #2 会把 a 符号变为 "a" 字符串对象,这时StringTable=["a"]0: ldc#2// String a// 这里的astore_1意思将#2的值放入局部变量池的第一位2: astore_1// ldc #3 会把 b 符号变为 "b" 字符串对象,这时StringTable=["a","b"]3: ldc#3// String b5: astore_2// ldc #4 会把 ab 符号变为 "ab" 字符串对象,这时StringTable=["a","b","ab"]6: ldc#4// String ab8: astore_3// 接下来的操作都是针对String s4 = s1 + s2;// 这里首先创建了一个StringBuilder类9: new#5// class java/lang/StringBuilder12: dup// 这里针对StringBuilder进行初始化13: invokespecial #6// Method java/lang/StringBuilder."<init>":()V16: aload_1// 这里对StringBuilder进行append方法,上面的aload_1意思是读取了第一个局部变量的值,相当于添加了"a"17: invokevirtual #7// Method java/lang/StringBuilder.append:20: aload_2// 这里对StringBuilder进行append方法,上面的aload_1意思是读取了第二个局部变量的值,相当于添加了"b"21: invokevirtual #7// Method java/lang/StringBuilder.append:// 这里对StringBuilder进行toString方法,相当于new了一个"ab",这时StringTable没有发生变化,但是堆产生了该值24: invokevirtual #8// Method java/lang/StringBuilder.toString:// 这里是针对String s5 = "a" + "b";操作,由于产生的结果为"ab",已经放在局部变量表里,所以直接读取即可// 注意:有StringTable的值只能有一个,所以这时StringTable=["a","b","ab"] 并没有发生变化27: astore429: ldc#4// String ab// 后面就是获得对应值然后比较31: astore533: getstatic#9// Field java/lang/System.out:Ljava/io/PrintStream;36: aload_337: aload539: if_acmpne4642: iconst_143: goto4746: iconst_047: invokevirtual #10// Method java/io/PrintStream.println:(Z)V50: returnLineNumberTable:line 11: 0line 12: 3line 13: 6line 14: 9line 15: 29line 17: 33line 21: 50// 局部变量池LocalVariableTable:StartLengthSlotNameSignature0510args[Ljava/lang/String;3481s1Ljava/lang/String;6452s2Ljava/lang/String;9423s3Ljava/lang/String;29224s4Ljava/lang/String;33185s5Ljava/lang/String;StackMapTable: number_of_entries = 2frame_type = 255 /* full_frame */offset_delta = 46locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream ]frame_type = 255 /* full_frame */offset_delta = 0locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ]stack = [ class java/io/PrintStream, int ]}SourceFile: "Demo1_22.java"

经验总结扩展阅读