少年帮|JVM内幕:Java虚拟机详解( 七 )

":()V#18 = Class#25//java/lang/System#19 = NameAndType#26:#27//out:Ljava/io/PrintStream;#20 = Utf8Hello#21 = Class#28//java/io/PrintStream#22 = NameAndType#29:#30//println:(Ljava/lang/String;)V#23 = Utf8org/jvminternals/SimpleClass#24 = Utf8java/lang/Object#25 = Utf8java/lang/System#26 = Utf8out#27 = Utf8Ljava/io/PrintStream;#28 = Utf8java/io/PrintStream#29 = Utf8println#30 = Utf8(Ljava/lang/String;)V这个常量池包含了下面的类型:
Integer4 字节常量Long8 字节常量Float4 字节常量Double8 字节常量String字符串常量指向常量池的另外一个包含真正字节 Utf8 编码的实体Utf8Utf8 编码的字符序列字节流Class一个 Class 常量 , 指向常量池的另一个 Utf8 实体 , 这个实体包含了符合 JVM 内部格式的类的全名(动态链接过程需要用到)NameAndType冒号(:)分隔的一组值 , 这些值都指向常量池中的其它实体 。 第一个值(“:”之前的)指向一个 Utf8 字符串实体 , 它是一个方法名或者字段名 。 第二个值指向表示类型的 Utf8 实体 。 对于字段类型 , 这个值是类的全名 , 对于方法类型 , 这个值是每个参数类型类的类全名的列表 。 Fieldref, Methodref, InterfaceMethodref点号(.)分隔的一组值 , 每个值都指向常量池中的其它的实体 。 第一个值(“.”号之前的)指向类实体 , 第二个值指向 NameAndType 实体 。
异常表异常表像这样存储每个异常处理信息:

  • 起始点(Start point)
  • 结束点(End point)
  • 异常处理代码的 PC 偏移量
  • 被捕获异常的常量池索引
如果一个方法有定义 try-catch 或者 try-finally 异常处理器 , 那么就会创建一个异常表 。 它为每个异常处理器和 finally 代码块存储必要的信息 , 包括处理器覆盖的代码块区域和处理异常的类型 。
当方法抛出异常时 , JVM 会寻找匹配的异常处理器 。 如果没有找到 , 那么方法会立即结束并弹出当前栈帧 , 这个异常会被重新抛到调用这个方法的方法中(在新的栈帧中) 。 如果所有的栈帧都被弹出还没有找到匹配的异常处理器 , 那么这个线程就会终止 。 如果这个异常在最后一个非守护进程抛出(比如这个线程是主线程) , 那么也有会导致 JVM 进程终止 。
Finally 异常处理器匹配所有的异常类型 , 且不管什么异常抛出 finally 代码块都会执行 。 在这种情况下 , 当没有异常抛出时 , finally 代码块还是会在方法最后执行 。 这种靠在代码 return 之前跳转到 finally 代码块来实现 。
符号表除了按类型来分的运行时常量池 , Hotspot JVM 在永久代还包含一个符号表 。 这个符号表是一个哈希表 , 保存了符号指针到符号的映射关系(也就是 Hashtable) , 它拥有指向所有符号(包括在每个类运行时常量池中的符号)的指针 。
引用计数被用来控制一个符号从符号表层移除的过程 。 比如当一个类被卸载时 , 它拥有的在常量池中所有符号的引用计数将减少 。 当符号表中的符号引用计数为 0 时 , 符号表会认为这个符号不再被引用 , 将从符号表中卸载 。 符号表和后面介绍的字符串表都被保存在一个规范化的结构中 , 以便提高效率并保证每个实例只出现一次 。
字符串表Java 语言规范要求相同的(即包含相同序列的 Unicode 指针序列)字符串字面量必须指向相同的 String 实例 。 除此之外 , 在一个字符串实例上调用 String.intern() 方法的返回引用必须与字符串是字面量时的一样 。 因此 , 下面的代码返回 true:
("j" + "v" + "m").intern() == "jvm"Hotspot JVM 中 interned 字符串保存在字符串表中 。 字符串表是一个哈希表 , 保存着对象指针到符号的映射关系(也就是Hashtable) , 它被保存到永久代中 。 符号表和字符串表的实体都以规范的格式保存 , 保证每个实体都只出现一次 。