JVM-常量池篇

ragnar 1年前 ⋅ 214 阅读

如果你是一个java开发工程师,那你一定有接触过这个池那个池的。现在我们来聊聊常量池,包括:Class常量池、运行时常量池、字符串常量池,以及基本数据类型包装类的对象池。

1 Class常量池 & 运行时常量池

1.1 什么是Class常量池?

Class常量池:也就是Class文件里的常量池(Constant pool table),用来存放编译期生成的各种字面量(Literal)、和符号引用(Symbolic References)。

字面量:指的是由字母、数字等构成的字符串或者数值常量
比如:“hello world”、12、0.2等等。

符号引用

  • 接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

也许这样的概念过于抽象了,来,我们换一种方式。

1.1.1 命令行工具javap

java代码编程后就是Class文件,是一个字节码文件,阅读性很低,所以我们需要借助javap这个工具。

javap是JDK包里提供了命令行工具,可以帮助我们反编译一个或多个class文件。

首先,我们先了解一下这个工具的基本使用:javap -help输出它的用法。-v选项是我们需要的,因为Class常量池的信息是包含在附加信息里。

D:/> javap -help
用法: javap <options> <classes>
其中, 可能的选项包括:
  -help  --help  -?        输出此用法消息
  -version                 版本信息
  -v  -verbose             输出附加信息
  -l                       输出行号和本地变量表
  -public                  仅显示公共类和成员
  -protected               显示受保护的/公共类和成员
  -package                 显示程序包/受保护的/公共类
                           和成员 (默认)
  -p  -private             显示所有类和成员
  -c                       对代码进行反汇编
  -s                       输出内部类型签名
  -sysinfo                 显示正在处理的类的
                           系统信息 (路径, 大小, 日期, MD5 散列)
  -constants               显示最终常量
  -classpath <path>        指定查找用户类文件的位置
  -cp <path>               指定查找用户类文件的位置
  -bootclasspath <path>    覆盖引导类文件的位置

1.1.2 反编程class文件

现在我们定义一类:

public class HelloWorld {
	private String name;

	public HelloWorld(String name){
		this.name = name;
	}

	public void say() {
		System.out.println(this.name + ": hello world");
	}

	public static void main(String[] args){
		HelloWorld h = new HelloWorld("Rollo");
		h.say();
	}
}

用javac编译后,我们就得到了class文件,执行javap -v HelloWorld.class,就能得到以下信息:
【下面的Constant pool就是Class常量池,是一个映射表(key-value)的结构。 比如:在#11 = Utf8 name这一行中,name是一个字面量#11是一个符号引用。】 class常量池.png

1.2 什么是运行时常量池?

运行时常量池:JVM在运行时,会在方法区(JDK8后叫“元数据区”)动态生成一个存储常量的区域,用来存放编译时生成的各种字面量符号引用
类加载过程中,Class常量池的内容会复制到运行时常量池,这个类加载过程还会把符号引用转换成为直接引用(内存地址)。

2 字符串常量池

2.1 为什么JVM要做字符串常量池?

  • 相同字符经常会反复出现;
  • 字符串对象的创建和销毁都有开销。

因此,把代码中出现的字符串池化是最优解。

另外,不同版本JDK的字符串常量池所在区域会有所差别。

2.2 JDK6及以前-字符串常量池的实现

字符串常量池_1.6.png 方法区运行时常量池包含着字符串常量池

String s1 = new String("aaa") ===> 在堆区新建一个对象
String s2 = s1.intern() ===> 指向字符串池的引用(因为字符串常量池还没有这个常量,因此会在方法区新创建)
String s3 = "aaa" ===> 指向字符串池的引用

2.3 JDK7及以后-字符串常量池的实现

字符串常量池_1.7.png

字符串常量池被移到了堆区

String s1 = new String("aaa") ===> 在堆区新建一个对象
String s2 = s1.intern() ===> 指向字符串常量池的引用(字符串常量池还没有这个常量,常量池会指向s1)
String s3 = "aaa" ===> 指向字符串池的引用

3 基本数据类型的包装类和对象池

基本数据类型有哪些?
一共有8种:byte / char / short / int / long / float / double / boolean

这些基本数据类型都有包装类,而这些包装类基本都有实现常量池技术,也就是所谓的对象池。对象池的本质就是把一定范围内的对象预先创建存储起来,方便直接取用。

8个基本数据中有6个实现了对象池:

  • Byte 范围:[-128, 127]
  • Short 范围:[-128, 127]
  • Integer 范围:[-128, 127]
  • Long 范围:[-128, 127]
  • Boolean 范围:true / false
  • Character 范围:[0, 127]

如上,除了Boolean和Character有点特殊外,其它4个都池化了-128到127的对象。用这个对象池有个前提,那就是要调用他们的valueOf方法来创建对象,才能用得上。


全部评论: 0

    我有话说:

    目录