文章目录
参考文章:
JDK8的JVM内存结构,元空间替代永久代成为方法区及常量池的变化
静态常量池与运行时常量池
一、JDK版本变化
- Java6和6之前,常量池是存放在方法区(永久代)中的。
- Java7,将常量池是存放到了堆中。
- Java8之后,取消了整个永久代区域,取而代之的是元空间。运行时常量池和静态常量池存放在元空间中,而字符串常量池依然存放在堆中。
二、常量池类型
在Java的内存分配中,总共3种常量池:
> 字符串常量池(String Constant Pool)
1. 字符串常量池在Java内存区域的哪个位置?
在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;
在JDK7.0版本,字符串常量池被移到了堆中了。至于为什么移到堆内,大概是由于方法区的内存空间太小了。
在永久代移除后,字符串常量池也不再放在永久代了,但是也没有放到新的方法区——元空间里,而是留在了堆里。运行时常量池当然是随着搬家到了元空间里,毕竟它是装类的重要等信息的,有它的地方才称得上是方法区。
2. 字符串常量池是什么?
在HotSpot VM里实现的String Pool功能的是一个StringTable类,它是一个Hash表,默认值大小长度是1009;这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享。字符串常量由一个一个字符组成,放在了StringTable上。
在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String#intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;
在JDK7.0中,StringTable的长度可以通过参数指定:
-XX:StringTableSize=66666
3. 字符串常量池里放的是什么?
在JDK6.0及之前版本中,String Pool里放的都是字符串常量;
在JDK7.0中,由于String#intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。
需要说明的是:字符串常量池中的字符串只存在一份!
如:
String s1 = “hello,world!”;
String s2 = “hello,world!”;
即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。
字符串常量池使用示例
> class常量池(Class Constant Pool)
1. class常量池简介
我们写的每一个Java类被编译后,就会形成一份class文件;class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References);
每个class文件都有一个class常量池。
2. 什么是字面量和符号引用
字面量包括:1. 文本字符串 2. 八种基本类型的值 3. 被声明为final的常量等;
符号引用包括:1. 类和方法的全限定名 2. 字段的名称和描述符 3. 方法的名称和描述符。
> 运行时常量池(Runtime Constant Pool)
运行时常量池存在于内存中,也就是class常量池被加载到内存之后的版本,不同之处是:它的字面量可以动态的添加(String#intern()),符号引用可以被解析为直接引用;
JVM在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。
而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。
在解析阶段,会把符号引用替换为直接引用,解析的过程会去查询字符串常量池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与字符串常量池中是一致的。