class 常量池、字符串常量池和运行时常量池的区别


class 常量池、字符串常量池和运行时常量池的区别

概念

常量池(Constant Pool)

常量池(Constant Pool),也叫 class 常量池(Class Constant Pool)。

java文件被编译成 class 文件,class 文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项就是常量池(Constant Pool),用于存放编译器生成的各种字面量( Literal )和 符号引用(Symbolic References)。

在这里插入图片描述

字面量( Literal ):就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。

符号引用(Symbolic References):是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。

符号引用一般包括下面三类常量:

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

描述符:是指描述字段或方法的类型的字符串。

在这里插入图片描述

常量池中数据项类型

常量池的每一项常量都是一个表,一共有如下表所示的11种各不相同的表结构数据,这每个表开始的第一位都是一个字节的标志位(如下所示),代表当前这个常量属于哪种常量类型。

序号数据项类型类型标志类型描述
1CONSTANT_Utf81UTF-8 编码的Unicode字符串
2CONSTANT_Integer3int 类型字面值
3CONSTANT_Float4float 类型字面值
4CONSTANT_Long5long 类型字面值
5CONSTANT_Double6double 类型字面值
6CONSTANT_Class7对一个类或接口的符号引用
7CONSTANT_String8String 类型字面值
8CONSTANT_Fieldref9对一个字段的符号引用
9CONSTANT_Methodref10对一个类中声明的方法的符号引用
10CONSTANT_InterfaceMethodref11对一个接口中声明的方法的符号引用
11CONSTANT_NameAndType12对一个字段或方法的部分符号引用

字符串常量池(String Pool)

字符串常量池(String Pool),即 String Literal Pool , 又叫全局字符串池。

在类加载完成,经过验证,准备阶段之后,在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到 String Pool 中。

String Pool 中存的是引用值,而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放。

在 HotSpot VM 里实现的 String Pool 功能的是一个 StringTable 类,它是一个哈希表,里面存的是 驻留字符的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个 StringTable 引用之后就等同被赋予了”驻留字符串”的身份。这个 StringTable 在每个 HotSpot VM 的实例只有一份,被所有的类共享。

运行时常量池( Runtime Constant Pool )

jvm 在执行某个类的时候,必须经过加载、连接(验证、准备、解析)、初始化。而当类加载到内存中后,jvm 就会将 class 常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。

class 常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析之后,也就是把符号引用替换为直接引用,解析的过程会去查询字符串常量池 ,也就是 StringTable,以保证 运行时常量池所引用的字符串与 字符串常量池中所引用的是一致的。

举例说明:

public class StrTest {

    public static void main(String []args) {
		String str1 = "abc"; 
		String str2 = new String("def"); 
		String str3 = "abc"; 
		String str4 = str2.intern(); 
		String str5 = "def"; 
		System.out.println(str1 == str3);//true 
		System.out.println(str2 == str4);//false 
		System.out.println(str4 == str5);//true
    }
}

程序的内存分配过程:

  1. 首先,在堆中会有一个 abc 实例对象,全局 StringTable 中存放着 abc 的一个引用值。
  2. 然后,运行第二句的时候会生成两个实例,一个是 def 的实例对象,并且 StringTable 中存储一个 def 的引用值,还有一个是 new 出来的一个 def 的实例对象 。
  3. 与上面那个是不同的实例,当在解析 str3 的时候查找 StringTable,里面有 abc 的全局驻留字符串引用,所以str3的引用地址与之前的那个已存在的相同 。
  4. str4 是在运行的时候调用 intern() 函数,返回 StringTable 中 def 的引用值,如果没有就将 str2 的引用值添加进去,在这里,StringTable 中已经有了 def 的引用值了,所以返回上面在 new str2 的时候添加到 StringTable 中的 def 引用值。
  5. 最后 str5 在解析的时候就也是指向存在于 StringTable 中的 def 的引用值。

class常量池、字符串常量池和运行时常量池的流转

  1. 首先,经过编译之后,在该类的 class 常量池中存放一些符号引用;
  2. 然后类加载之后,将 class 常量池中存放的符号引用转存到 运行时常量池中;
  3. 然后经过验证,准备阶段之后,在堆中生成驻留字符串的实例对象(也就是上例中 str1 所指向的 ”abc” 实例对象),然后将这个对象的引用存到全局 String Pool 中,也就是 StringTable 中;
  4. 最后在解析阶段,要把运行时常量池中的符号引用替换成直接引用,那么就直接查询 StringTable,保证 StringTable 里的引用值与运行时常量池中的引用值一致,大概整个过程就是这样了。

总结

  • class 常量池 是在编译的时候每个 class 都有的,在编译阶段,存放的是常量的 符号引用 。
  • 字符串常量池在每个 jvm 中只有一份,存放的是字符串常量的引用值 。
  • 运行时常量池是在类加载完成之后,将每个 class 常量池中的符号引用值转存到运行时常量池中,也就是说,每个 class 都有一个运行时常量池 ,类在解析阶段 ,将符号引用替换成直接引用 ,与字符串常量池中的引用值保持一致。

方法区的 class 文件信息,class 常量池和运行时常量池的三者关系

在这里插入图片描述

方法区 class 文件信息

可以看到在方法区里的 class 文件信息包括:魔数,版本号,常量池,类,父类和接口数组,字段,方法等信息,其实类里面又包括字段和方法的信息。

class常量池

在这里插入图片描述

运行时常量池

下面是原 java 代码 :

public class TestInt {    
	private  String str = "hello";	
   	void printInt(){
  		System.out.println(65535);
  	}  
}

编译后得到 class 文件,经过反编译后得到如下信息:

在这里插入图片描述

可以看出被反编译的 class 文件中的内容和上面所说的是能对应上的。

class文件常量池和运行时常量池的关系以及区别

class 文件常量池存储的是当 class 文件被 jvm 加载进来后存放在方法区的一些字面量和符号引用,字面量包括字符串、基本类型的常量。

运行时常量池是当 class 文件被加载完成后,jvm 会将 class 文件常量池里的内容转移到运行时常量池里,在 class 文件常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。

总结

方法区里存储着 class 文件的信息和运行时常量池,class 文件的信息 包括类信息 和 class文件常量池 。

运行时常量池里的内容除了是 class 文件常量池里的内容外,还将 class 文件常量池里的符号引用转变为直接引用,而且运行时常量池里的内容是能动态添加的。例如调用 String 的 intern 方法就能将 string 的值添加到字符串常量池中,这里字符串常量池是包含在运行时常量池里的,但在 jdk1.8 后,将字符串常量池放到了堆中。

  • 7
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: 字符串常量池JVM中属于方法区(也称为永久代)内存分区。方法区是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。字符串常量池是方法区中的一部分,用于存储字符串常量。在JDK 8之后,方法区被取消了,取而代之的是元空间(MetaSpace),但字符串常量池仍然存放在元空间中。 ### 回答2: 字符串常量池JVM的方法区(也称为非堆区)中。 JVM将内存分为几个不同的区域,包括堆区、方法区、虚拟机栈等。而字符串常量池是方法区的一部分,用于存储在程序中直接使用的字符串常量。 在Java中,字符串常量池是一种特殊的内存存储区域,用于存储字符串常量,它的作用是提高字符串的重用性和效率。当我们使用双引号声明一个字符串时,JVM会首先在字符串常量池中查找是否存在相同内容的字符串,如果存在则直接返回引用,如果不存在则创建一个新的字符串并放入字符串常量池中。这种机制可以减少内存占用,提高程序的执行效率。 由于字符串常量池位于方法区,它是与其他线程共享的,在程序运行过程中,多个线程可以同时访问字符串常量池。而且,字符串常量池的位置是在程序的执行过程中被动态调整的,当字符串没有被引用时,JVM会自动回收字符串常量池中的空间。 总结来说,字符串常量池JVM的方法区的一部分,用于存储程序中直接使用的字符串常量,并提高字符串的重用性和效率。 ### 回答3: 字符串常量池JVM的方法区里。方法区是JVM的一个内存分区,用于存储类信息、常量、静态变量、即时编译器编译后的代码等。而字符串常量池就是方法区的一部分,用于存储字符串常量。 在Java中,当我们使用字符串字面量(如"hello")时,编译器会将其放入字符串常量池中。当程序执行时,如果再次使用相同的字符串字面量,JVM会直接从字符串常量池中取出已存在的字符串对象,而不会创建新的对象,这样可以节省内存空间。 由于字符串在Java中使用非常频繁,所以将字符串常量池放在方法区中,可以提高字符串的重用率。此外,字符串常量池的位置在方法区中也有利于GC(垃圾回收),因为当某个字符串不再被引用时,GC可以更方便地回收该字符串常量。 需要注意的是,从Java 7开始,字符串常量池被移出了PermGen空间(方法区的前身),并放置在堆中,这是因为字符串常量池中的字符串对象是可以被垃圾回收的,而且过多的字符串常量可能导致PermGen空间溢出的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违の欢喜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值