Java常量池理解

Java 的内存分配中,总共 3 种常量池,分别是:class 文件常量池(class constant pool)、运行时常量池(runtime constant pool)、字符串常量池(string pool)。

什么是常量

  • 用 final 修饰的成员变量表示常量,值一旦给定就无法改变。
  • final 修饰的变量有三种:静态变量、实例变量和局部变量,分别表示三种类型的常量。

class文件常量池

在Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用。

这里简单解释下字面量和符号引用

字面量

字面量类似与我们平常说的常量,主要包括:
1、文本字符串:就是我们在代码中能够看到的字符串,例如String a = “aa”。其中"aa"就是字面量。
2、被final修饰的变量。

符号引用

主要包括以下常量:

1、类和接口和全限定名:例如对于String这个类,它的全限定名就是java/lang/String/
2、字段的名称和描述符:所谓字段就是类或者接口中声明的变量,包括类级别变量(static)和实例级的变量。
3、方法的名称和描述符。所谓描述符就相当于方法的参数类型+返回值类型

运行时常量池

我们上面说的class文件中的常量池,它会在类加载后进入方法区中的运行时常量池。并且需要注意的是,运行时常量池是全局共享的,多个类共用一个运行时常量池。并且class文件中常量池多个相同的字符串在运行时常量池只会存在一份。

注意运行时常量池存在于方法区中。

字符串常量池

看名字我们就可以知道字符串常量池会用来存放字符串,也就是说常量池中的文本字符串会在类加载时进入字符串常量池。

那字符串常量池和运行时常量池是什么关系呢?上面我们说常量池中的字面量会在类加载后进入运行时常量池,其中字面量中有包括文本字符串,显然从这段文字我们可以知道字符串常量池存在于运行时常量池中。也就存在于方法区中。不过到了JDK1.7时,字符串常量池就被移出了方法区,转移到了堆里了

那么我们可以推断,到了JDK1.7以及之后的版本中,运行时常量池并没有包含字符串常量池,运行时常量池存在于方法区中,而字符串常量池存在于堆中。

总结一下字符串常量池在JVM内存区域的位置和JDK版本的关系:

  • 在 JDK6.0 及之前版本,字符串常量池是放在方法区(永久代)中,此时常量池中存储的是对象。
  • JDK7.0 版本,字符串常量池被移到了堆中,常量池存储的是引用。

分析问题


public static void main(String[] args){
    String t1 = new String("1");
    t1.intern();
    String t2 = "1";
    System.out.println(t1 == t2);

    String t3 = new String("2") + new String("2");
    t3.intern();
    String t4 = "22";
    System.out.println(t3 == t4);
}

答案输出:

JDK1.6是 false false

JDK1.7是 false true

String 的intern方法

在JDK1.6的时候,调用了这个方法之后,虚拟机会在字符串常量池在查找是否有内容与"tt"相等的对象,如果有,则返回这个对象,如果没有,则会在字符串常量池中添加这个对象。注意,是把这个对象添加到字符串常量池。

到了JDK1.7之后,如果调用了intern这个方法,虚拟机会在字符串常量池在查找是否有内容与"tt"相等的对象,如果有,则返回这个对象,如果没有。则会在堆中把这个对象的引用复制添加到字符串常量池中。注意,这个时候添加的是对象在堆中的引用。

输出结果分析
String t1 = new String("1")

这句代码执行之前,字符串常量池中已经有"1"这个对象,执行之后会在堆中也创建一个"1"的对象,此时t1指向的是堆中的对象。

t1.intern();

这句代码执行之后,会在字符串常量池寻早内容为"1"的对象,字符串常量池已经存在这个对象了,把这个对象返回(不过返回之后并没有变量来接收)。

t2 = "1"

这句执行后会在字符串常量池查找内容为"1"的对象,字符串常量池已经有这个对象了,返回给t2,此时t2指向的是常量池中的对象。

一个是常量池中的对象,一个是在堆中的对象,两者能相等吗?
因此在JDK1.6和JDK1.7环境下 t1 与 t2都是不相等

接着下面

t3 = new String("2") + new String("2");

这段代码调用之前,字符串常量池有一个"2"的对象,执行之后,实际上会调用StringBuilder的append()方法类进行拼接,最后在堆中创建一个"22"的对象,注意,此时字面量并没有"22"这个字符串,也就是说在字符串常量池并没有"22"这个对象。此时t3指向堆中"22"这个对象

t3.intern();

执行这个方法之后

JDK1.6

它在字符串常量池中并没有找到内容为"22"的对象,所以这个时候会把这个对象添加到字符串常量池,并把这个对象返回(此时并没有变量来接收这个返回的对象)。注意添加的是对象,而并非引用。

t4 = "22"

这句代码执行后,会返回字符串常量池中内容为"22"对象,此时t4指向的是字符串常量池中的对象。

显然,一个对象在字符串常量池,一个在堆中,两个对象并非是同一个对象,因此在JDK1.6的时候,t3与t4不相等

JDK1.7

t3.intern()执行之后,由于在字符串常量池在并没有内容为"22"的对象,所以会把堆中该对象的引用赋值到字符串常量池。注意此时字符串常量池保存的是堆中这个对象的引用

t4 = "22"

执行这句代码之后,从字符串常量池返回给t4的是堆中对象的引用。此时t4指向的实际上是堆中对象的引用,也就是说,t3和t4指向的是同一个对象。因此在JDK1.7的时候,t3与t4相等

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值