前言
本文章来自网上各方大佬笔记,如有侵权,敬请告知
JVM常量池主要分为
class文件常量池
、运行时常量池
、全局字符串常量池
及基本类型包装类对象常量池
class文件常量池
class文件是一组以字节为单位的二进制数据流,在
.java
文件编译过程中,.class
文件会以二进制数据的形式存放在磁盘中,这些二进制数据中就包括class文件常量池
。
所以说,class文件常量池
是非运行时常量池,其在编译阶段便已确定
既然是常量池,其中自然是用来存放常量的,
class文件常量池
主要存放两大常量:- 字面量(主要包括) - 文本字符串 也即:public String s = "123"; 中的 "123" - 用final修饰的成员变量 包括静态变量、实例变量和局部变量 而对于基本数据类型、方法中的局部变量,class文件常量池中只会保留它的字段描述符(即数据类型描述符,int的是I)、字段的名称value,而它们的字面量不会存在于常量池 - 符号引用 - 类和接口的全限定名 如:java/lang/String; 类和接口的全限定名会将类名/接口名中的 . 替换为 / ,以便在运行时解析得到类的直接引用 - 字段的名称和描述符 字段也就是类/接口中声明的变量,分为类级别变量和实例级变量 方法中的局部变量名在class文件常量池中仅保存字段名 - 方法中的名称和描述符 即参数类型+返回值
运行时常量池
运行时常量池
是方法区的一部分,所以也是全局贡献的
JVM在执行某个类时,会依次进行:加载
、链接(验证、准备、解析)
、初始化
在加载
步骤中,JVM需要完成:- 通过一个类的全限定名来获取此类的二进制字节流 - 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构 - 在内存中生成一个类对象,代表加载的这个类,这个对象是java.lang.Class,它作为方法区这个类的各种数据访问的入口
类对象和普通对象是不同的。类对象是在类加载的时候完成的,是JVM创建的并且是
单例
的,类对象会作为这个类和外界交互的入口;普通对象则是在调用new之后创建
加载
的第二步,即为将class字节流
所代表的静态存储结构转化为方法区的运行时数据结构,其中就包括class文件常量池
进入运行时常量池
的过程
当不同类共用一个运行时常量池
时,各类的class文件常量池
会在进入运行时常量池
的过程中,各个class文件常量池
中的相同字符串只会在在运行时常量池
存下一份,实现进一步优化
运行时常量池
的作用是存储class文件常量池
中的符号信息,运行时常量池中保存着一些class文件中描述的符号引用,同时在类的解析阶段还会将这些符号引用翻译出直接引用(直接指向实例对象的指针,内存地址)
,翻译出来的直接引用也是存储在运行时常量池
中
运行时常量池
相对于class文件常量池
的一大特征就是具有动态性,java规范并不要求常量只能在运行时产生,也即运行时常量池
中的内容并不完全来自class文件常量池
,在运行的过程中也可以通过代码生成常量并将其放入运行时常量池
中,这种特性被使用最多的就是String.intern()
全局字符串常量池
Java中创建字符串对象的方式一般有如下两种:
- String s_1 = "Hellow"; - String s_2 = new String("Hellow");
第一种方式声明的
字面量 Hellow
是在编译器
就已经确定的,它会直接进入class文件常量池
中,当运行期间在全局字符串常量池
中保存它的一个引用,实际上最终还是要在堆
上创建一个“Hellow”对象
第二种方式使用了new String()
,也就是调用了String类的构造函数,new指令的功能是创建一个类的实例对象并完成加载初始化,因此这个字符串对象是在运行期才能确定的,创建的字符串的对象是在堆
上
因此,System.out.println(s_1 == s_2);
的结果必定为false,虽然它们都在堆
中,但所处位置地址肯定不同