最近在看周志明的《深入理解 Java 虚拟机》这本书,不得说,学习 JVM 对更深层次理解 Java 有很大的帮助,两者互相印证。像打通了任督二脉一般,对 OOP 和 语法这块为什么要这么设计,语法为什么要这么写豁然开朗,融汇贯通后也更便于记忆这些知识点。

文章大多摘自自己的 Wiki JavaBasic,都是对知识点的总结,加上自己的理解后一条条写下的。难免有很多理解错误的地方,自己前前后后也纠正了不少,也是一个学习的过程。

关于常量池这块,本来就知道一个字符串常量池,可是看了 JVM 后,又冒出了好几个常量池,顿时懵逼了。看了大量资料,理解后总结出了这么点知识。

常量池的划分

  • Class 文件常量池
  • 运行时常量池
  • 字符串常量池

Class 文件常量池

Class 文件常量池指的是编译生成的 class 字节码文件,其结构中有一项是常量池(Constant Pool Table),用于存放编译期生成的各种字面量符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

什么是字面量和符号引用呢,为了理解这两个概念,只能啰嗦的写了。

  • 这里的字面量是指字符串字面量和声明为 final 的(基本数据类型)常量值,这些字符串字面量除了类中所有双引号括起来的字符串(包括方法体内的),还包括所有用到的类名、方法的名字和这些类与方法的字符串描述、字段(成员变量)的名称和描述符;声明为final的常量值指的是成员变量,不包含本地变量,本地变量是属于方法的。这些都在常量池的 UTF-8 表中(逻辑上的划分)
  • 符号引用,就是指指向 UTF-8 表中向这些字面量的引用,包括类和接口的全限定名(包括包路径的完整名)、字段的名称和描述符、方法的名称和描述符。只不过是以一组符号来描述所引用的目标,和内存并无关,所以称为符号引用,直接指向内存中某一地址的引用称为直接引用

运行时常量池

运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。

一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。

可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。

字符串常量池

  • 在 jdk1.6(含)之前也是方法区的一部分,并且其中存放的是字符串的实例
  • 在 jdk1.7(含)之后,是在堆内存之中,存储的是字符串对象的引用,字符串实例是在堆中
  • jdk1.8 已移除永久代,字符串常量池是在本地内存当中,存储的也只是引用

字符串常量池是全局的,JVM 中独此一份,因此也称为全局字符串常量池。

运行时常量池中的字符串字面量若是成员的,则在类的加载初始化阶段就使用到了字符串常量池;若是本地的,则在使用到的时候(执行此代码时)才会使用到字符串常量池。

其实,“使用常量池”对应的字节码是一个 ldc 指令,在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用(jdk1.7)。String 类的 intern() 方法还可在运行期间把字符串放到字符串常量池中。

JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型剩余的6种基本数据类型的包装类,都使用了缓冲池技术,但是 Byte、Short、Integer、Long、Character 这5种整型的包装类也只是在对应值在 [-128,127] 时才会使用缓冲池,超出此范围仍然会去创建新的对象。

总结

理解了这部分后,后面才能更容易的理解 JVM 中方法的调用,多态的实现等。