憨人笔记之JVM-常量池区分

238 阅读5分钟

常量池的区分

说到常量池,在JVM中,在说类加载子系统时有说到class类常量池,在分析运行时数据区时有说到运行时常量池。对于不同类型的常量池,可能容易产生混淆。那么下面来说说在整个JVM体系中各种常量池的概念以及异同。

常量池实际上有三类:class类常量池、运行时常量池、字符串常量池,下面就分别介绍一下各种常量池。

class类常量池

class类常量池是最先接触到的常量池概念。它主要存储的是由java编译后的class文件中的各种字面量符号引用。我们都知道,一个class文件除了常量池以外,还有很多内容,例如类的版本、字段、方法、接口等描述信息。class文件是作为程序运行的一个最基本的组层。

字面量:所谓字面量就是经常所说的常量的概念。常量即为程序的整个运行过程中值保持不变的量。对于常量,需要注意一下与常量值的区别。常量是形式化的表现,而常量值是指常量的具体和直观的表现形式。

常量有三种类型:静态常量、成员常量、和局部常量。

public class HelloWorld{
  // 静态常量
  public static final double PI = 3.14;
  // 成员常量
  final int x = 10;
  
  public static void main(String[] args){
    // 局部常量
    final int y = 5;
  }
}

在Java中,字面量(常量)的体现主要有以下类型:文本字符串、被final修饰的变量、基本数据类型的值

符号引用:符号引用指的是一组符号用来描述所引用的目标。一般包括三类常量:类的全限定名、方法名和喵舒服、字段名和描述符。

类常量池在class文件中的体现包含两个部分,常量池数量和常量池表

运行时常量池

在经过类加载子系统对类进行加载初始化之后,就进入到了运行时数据区。运行时常量池是属于方法区的一部分,是线程共享的区域,也会抛出OOM异常。

在说类加载这个阶段,首先通过类的全限定名加载class文件,然后再将类的静态存储结构保存到运行时数据区。这个过程实际上就是将class常量池中的数据保存到方法区中的运行时常量池中。这也就意味着每个类都有一个运行时常量池。刚刚说到class常量池中存的是字面量和符号引用,也就是说存的并不是对象实例,而是对象的符号引用值。在类的解析阶段,会把符号引用替换成直接引用,解析过程就会去查询全局字符串池,保证运行时常量池中所引用的字符串与全局字符串池中的引用是一致的。

字符串常量池

字符串常量池也叫做全局字符串池。全局字符串中的内容在类加载完成,经过验证、准备阶段之后在堆中生成字符串对象的实例,然后将该字符串对象实例的引用存到字符串常量池中(StringPool),注意,是将字符串对象实例的引用存到字符串常量池中。也就是说字符串常量池中实际上存的都是字符串对象的引用。

在HotSpot VM中是通过一个StringTable来实现StringPool功能的。它是一个哈希表。里面就保存了字符串(用双引号引起来的内容)的引用。StringTable是被所有类共享的。

下面我们看一段代码:

String str = "abc";

我们在说类常量池的时候说到,在加载阶段,会将类常量池中的信息加载到运行时常量池,主要就是字面量及符号引用。在上面这段代码中"hello"字符串将会被保存到常量池中,同时会在字符串常量池中维护一个StringTable记录该字符串对象实例的引用。变量str最终会指向该字符串对象实例的引用。

接下来再看一段代码:

String str1 = new String("def");

首先我们来分析一下会创建几个对象。就单独把这行代码拎出来讲,会创建两个对象。首先在常量池中查找是否存在"def"对象,不存在,则会在常量池中创建"def"并返回对象的引用。同时在堆中new String("def")对象,将对象的地址赋值给str1,创建一个引用。

总结

  1. 在字符串常量池中,维护的是字符串常量的引用且一个虚拟机当中只会存在一份,是所有类共享的。
  2. class常量池是在编译的时候所产生的,每个类都会有一个类常量池,通常存放的是符号引用。
  3. 运行时常量池是在类加载完成之后,将class常量池中的符号引用转换成运行时常量池中的值,在类解析之后,会讲运行时常量池中的符号引用转换成直接引用。

不怕路歹行不怕大雨淋,心上一字敢 面对我的梦,甘愿来作憨人。 --<憨人>