五花缭乱的常量池

Class常量池(静态常量池)

在class文件中,除了有类的版本,字段,方法和接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期的字面量和符号引用。(此处指的常量池并不是我们平时所说的运行时数据区的内存,而是在java文件编译成class文件时,存放在class文件中的内存区域)

图片1.png

字面量

给基本类型或String类型变量赋值的方式就叫做字面量或者字面值(不包括对象)。

比如String a = “b”,这里的b就是字面量,类似的还有int a = 1 ,1是字面量等。

符号引用

符号引用以一组符号来描述所引用的目标。符号引用可是任何形式的字面量,JAVA在编译的时候会被编译成一个class文件,但在编译的时候虚拟机并不知道引用类的实际地址(此时的类就是一个名字,名字可以用任意字面量表示。)在类解析阶段必须根据这个名字找的到类的实际地址从而转为直接引用。因此符号引用存在的意义就是可以根据这个符号找到类真正的地址。(简单来说这个地址符号引用是类加载阶段,我们的需要根据符号引用class文件的数据,然后再分配好内存后转为直接引用。直接引用才是我们运行时数据区需要用到的引用)。

运行时常量池(存储类型信息,常量,静态变量)

运行时常量池是每一个类或者接口在JVM运行时的表现形式。它包括了若干不同的常量:从编译期可知的数值字面量到必须运行期解析后才能获得的方法或字段引用。

运行时常量池是在类加载之后生成的,将class常量池中的符号引用值转存到运行时常量池中,在类解析之后,将符号引用替换为直接引用。运行时常量池在JDK1.7之后,就移动到堆内存之中。这里指的是物理空间,而逻辑空间还是属于方法区(方法区是逻辑分区)

在JDK1.8之后,使用元空间代替永久代来实现方法区(元空间直接操作直接内存)。但是逻辑上元空间依然属于方法区。

字符串常量池

这个常量池很有意思,被独立划分出来。没有和运行时常量池混成一个池子。为什么呢?应该是字符串在一个项目中出现的频率实在是太大了,导致被独立划分了一个池子。

当然也有不少地方说,字符串常量池就是属于运行时常量池的一部分,官方没有给明确文档。但是我们应该从JVM设计它用于解决什么问题的角度来学习它。

String对象在Java语言中占据非常大的使用比例,因此特殊处理String的内存区域,可以提升系统整体性能。

String的真面目

String是对char数组进行了封装实现的对象,主要有两个成员变量:char数组,hash值。

String对象的不可变性

图片2.png

由图可见,String类被final修饰,char数组也被final修饰。类被final修饰代表该类不可继承,而char[]被final+private修饰,代表了String对象不可更改。

安全性

保证String对象的安全性,假设String对象是可变的,那么String对象将被恶意修改。

避免hash重复更改

保证hash值不会频繁更改,确保了唯一性,使得类似HashMap容器才能实现的key-value缓存功能。

创建字符串的三种方式

String s1 = "helloWorld";
String s2 = new String("helloWorld");
String s3 = new String("helloWorld").intern();

字符串的创建方式对比

看完了千篇一律的博客,是不是感觉对这个知识点并没有特别清晰的认识?没事听在下娓娓道来。

面试常问问题

问题1

String s = "helloWorld";定义了几个对象?

Java运行环境有一个字符串池,由String类维护。执行语句String s="abc"时,首先查看字符串池中是否存在字符串"abc",如果存在则直接将"abc"赋给s,如果不存在则先在字符串池中新建一个字符串"abc",然后再将其赋给str。

由此,我们得到下面的结论,不同的引用使用这个方法创建值相同的常量,引用的都是方法区中的同一个常量。

image.png

image.png

image.png

问题2

String s = new String("helloWorld");定义了几个对象?

执行语句String s=new String("helloWorld")时,不管字符串池中是否存在字符串"helloWorld",都会直接在堆中新建一个String对象。可以用下图进行理解:

image.png

image.png

image.png

 

问题3

如何理解String的intern方法?

当一个String实例调用intern()方法时,Java查找常量池中是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常量池中增加一个Unicode等于str的字符串并返回它的引用;

intern()有两个作用,第一个是将字符串字面量放入常量池(如果池没有的话),第二个是返回这个常量的引用。

可以用这个例子来做理解:

image.png

image.png

我们得到下面这个区别图。

image.png

 

intern()的使用场景

我们发现String s = "helloWorld";和String s = new String("helloWorld").intern();的效果看着非常类似。

实际上intern()可以理解为对String s = new String("helloWorld");这种格式的一种增强,避免它重复创建过多的对象。

这里是一个能展现出inern()实际作用的场景,首先假设我从数据库里读了一个人的信息出来,然后把这个人的名字赋值给这个Person对象.

那么,从数据库读数据,毫无疑问得创建一个字符串对象出来,假定读了10个人的数据,其中三个都叫小明,那么在不使用intern()的情况下,对字符串对象的引用情况如图所示

image.png

在使用intern的情况下,对字符串对象的引用情况如图所示

image.png

很显然,剩下的那两个小明字符串对象,就都可以回收了,大大节省空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大将黄猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值