前言
“常量池”,这个光听概念其实应该是很好理解的,就是用来存放常量的地方。常量就是无法进行改变的那些量。
但是java中有许许多多的“常量池”,有的时候乍一听还反应不过来,今天就打算梳理一下这些常量池。
class常量池
随便写一个hello world的程序,然后编译,使用javap命令查看,你会发现字节码文件中就有一个class常量池:
可以看到里面存放了各种字面量(如Hello World
,应该在#18中)和各种符号引用(如#10
)
显然由于每一个类是一个字节码文件,所以这个class常量池是每个类独享的。
运行时常量池
上面说的class常量池,是字节码文件的常量池,本质上是存在与磁盘内的。我们都知道进程是无法直接使用磁盘内的东西的,所以必须要把这个磁盘中的class常量池加载到内存中才可以。而这个class常量池被加载到的地方,就叫做运行时常量池。
java中的运行时常量池位于方法区(方法区是共享空间哦),在JDK1.8之后方法区位于元空间,且元空间位于本地内存之中。
运行时常量池除了保存符号引用,还可能(因为没有强制要求,所以各家的JVM可以自己实现)有直接引用(你可以理解为指向内存的指针)。
运行时常量池除了可以通过由class文件的字节码文件常量池加载得到,还可以通过运行时动态的进行加入(String.intern()
)。
字符串常量池
这个应该是我们最先接触到,也是最多接触到的吧。String s = "a"
这样的语句,当时老师说的是在字符串常量池中有一个a
,然后接下来你再去声明别的String b = "a"
,会从字符串常量池中取出,所以字符串s和b的地址是一样的。
那么这个字符串常量池是在哪里呢?在JDK1.7之前是放在方法区(确切说应该是在方法区的运行时常量池)中的,而在JDK1.7之后被放到了堆中。
而且字符串常量池是全局唯一的,用下面的例子可以展示:
1 | public class Main { |
也就是从JDK1.7之后,所有的字符串对象就到了堆中,我们用一个例子可以证明:
1 | public static void main(String[] args) { |
这段代码会返回true和false。
首先第一个能返回true是因为现在的string已经在堆中了,所以str1.intern
返回的自然是这个字符串对象的地址了。
那第二个为什么会返回false呢?因为java这个字符串已经在字符串常量池存在了(系统放进去的),所以不符合首次出现。