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

然后我们运行之后,我们可以在out文件夹下找到其编译后程序:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package cn.itcast.jvm.t5;public class HelloWorld {public HelloWorld() {}public static void main(String[] args) {System.out.println("hello world");}}我们在该目录下对其进行底层查看:
// 我们通过javap -v 代码名.class来查看其详细信息// 其中包括有:class文件的路径、最后修改时间、文件大?。焕嗟娜肪丁⒃矗╦ava)文件;常量池;常量定义、值;构造方法等javap -v HelloWorld.class// 我们查看其内部详细信息:// 这部分是class文件路径,最后修改日志,文件大小等信息Classfile /E:/编程内容/JVM/资料-解密JVM/代码/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.classLast modified 2022-11-2; size 567 bytesMD5 checksum 8efebdac91aa496515fa1c161184e354Compiled from "HelloWorld.java"// 这部分是全路径,源码等public class cn.itcast.jvm.t5.HelloWorldminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER// 这部分是常量池:我们可以看到很多东西 , 注解是IDEA为我们携带的// 首先我们可以看到最前面的#,这个代表这一行的地址,然后如果我们希望看懂这一行的信息,需要根据后面的#查看对应的行号// 我们到对应的行号去寻找,直到最后我们可以看到utf8形式的结果 , 我们将这些信息组合起来就是该行后面IDEA为我们注释的信息Constant pool:#1 = Methodref#6.#20// java/lang/Object."<init>":()V#2 = Fieldref#21.#22// java/lang/System.out:Ljava/io/PrintStream;#3 = String#23// hello world#4 = Methodref#24.#25// java/io/PrintStream.println:(Ljava/lang/String;)V#5 = Class#26// cn/itcast/jvm/t5/HelloWorld#6 = Class#27// java/lang/Object#7 = Utf8<init>#8 = Utf8()V#9 = Utf8Code#10 = Utf8LineNumberTable#11 = Utf8LocalVariableTable#12 = Utf8this#13 = Utf8Lcn/itcast/jvm/t5/HelloWorld;#14 = Utf8main#15 = Utf8([Ljava/lang/String;)V#16 = Utf8args#17 = Utf8[Ljava/lang/String;#18 = Utf8SourceFile#19 = Utf8HelloWorld.java#20 = NameAndType#7:#8// "<init>":()V#21 = Class#28// java/lang/System#22 = NameAndType#29:#30// out:Ljava/io/PrintStream;#23 = Utf8hello world#24 = Class#31// java/io/PrintStream#25 = NameAndType#32:#33// println:(Ljava/lang/String;)V#26 = Utf8cn/itcast/jvm/t5/HelloWorld#27 = Utf8java/lang/Object#28 = Utf8java/lang/System#29 = Utf8out#30 = Utf8Ljava/io/PrintStream;#31 = Utf8java/io/PrintStream#32 = Utf8println#33 = Utf8(Ljava/lang/String;)V// 这部分是编译后的代码,我们可以看到里面包含了#号,这些#就对应着上面的常量池 , 他们从常量池中获得相关信息用于代码中{public cn.itcast.jvm.t5.HelloWorld();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/t5/HelloWorld;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream;3: ldc#3// String hello world5: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8// 这里是局部变量表LocalVariableTable:StartLengthSlotNameSignature090args[Ljava/lang/String;}// 最后标上上述信息的来源文件SourceFile: "HelloWorld.java"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);}}

推荐阅读