JAVA常量池
常量池分类
常量池可以分为:字节码常量池,运行时常量池,字符串常量池。
字节码常量池(常量池表 Constant Pool Table)
字节码常量池在Class文件中,用于存放编译期生成的各种字面量与符号引用,主要存放两大类变量:字面量和符号引用。
- 字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等
- 符号引用是一组符号,用来描述所引用的目标。符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可
截止JDK13,字节码常量池中分别有17种不同类型的常量,17种常量类型所代表的具体含义如表6-3所示。
运行时常量池(Runtime Constant Pool)
字节码常量池将在类加载后存放到方法区的运行时常量池中。
对于运行时常量池,《Java虚拟机规范》并没有做任何细节的要求,不同提供商实现的虚拟机可以按照自己的需要来实现这个内存区域,不过一般来说,除了保存字节码常量池中描述的符号引用外(加载阶段),还会把由符号引用翻译出来的直接引用也存储在运行时常量池中(解析阶段)。
- 加载阶段会将类的二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构
- 解析阶段是Java虚拟机将运行时常量池内的符号引用替换为直接引用的过程
- 直接引用是可以直接指向目标的指针、相对偏移量或者是一个能间接定位到目标的句柄
运行时常量池相对于字节码常量池的另一个重要特征是具备动态性,Java语言并不要求常量一定只有编译器才能生成,也就是说,并非预置入字节码常量池的内容才能进入方法区运行时常量池,运行期间也可以将新的常量放入池中。
字符串常量池(String Table)
在《深入理解Java虚拟机》这本书上是这样写的:考虑到HopSpot未来的发展,在JDK6的时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了,到了JDK7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移至Java堆中,而到了JDK8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space)来代替,把JDK7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。
以下内容基于JDK8进行讨论,即字符串常量池位于运行时数据区的Java堆(Java Heap)中。
创建字符串对象有两种方式:
- 一种是直接赋值
-
String str = "Hello";
-
- 另一种是使用new关键字创建
-
String str = new String("Hello");
-
Java为了避免产生大量的字符串对象,设计了一个字符串常量池,通过初始化方式创建的字符串对象都会存于字符串常量池中,且字符串常量池中的字符串不会重复,以便可以共享使用,提高存储效率。
详解
String str1 = new String("Hello");①
String str2 = new String("Hello");②
String str3 = "Hello"; ③
String str4 = "Hello"; ④
System.out.println(str1 == str2); //false
System.out.println(str3 == str4); //true
System.out.println(str2 == str3); //false
System.out.println(str1.equals(str2)); //true
System.out.println(str3.equals(str4)); //true
String str5 = str4;⑤
System.out.println(str4 == str5); //true
str5 = "World";⑥
System.out.println(str4);//Hello
System.out.println(str5);//World
当代码运行到第①行后,JVM首先会查找字符串池中是否存在值为“Hello”的对象,发现不存在,则在字符串池中创建一个“Hello”对象,在堆内存中也创建一个“Hello”对象,并将堆内存中的“Hello”对象的引用(内存地址)赋给变量str1,此时字符串在内存中的存储示意图如下:
String str1 = new String("Hello");
当代码运行到第②行后,JVM首先会查找字符串池中是否存在值为“Hello”的对象,发现存在,则只在堆内存中创建一个值为“Hello”的对象,此时字符串在内存中的存储示意图如下:
String str2 = new String("Hello");
当代码运行到第③行后,JVM首先会查找字符串池中是否存在值为“Hello”的对象,发现存在该对象,则将该对象的引用赋给变量str3,此时字符串在内存中的存储示意图如下:
String str3 = "Hello";
当代码运行到第④行后,同理,会将“Hello”对象的引用赋给变量str4
String str4 = "Hello";
当代码运行到第⑤行后,会将str4的引用赋给str5,此时字符串在内存中的存储示意图如下:
String str5 = str4;
当代码运行到第⑥行后,JVM会首先查找字符串池中是否存在值为“World”的对象,发现不存在,则创建一个“World”对象,并将该对象的引用赋给变量str5,此时字符串在内存中的存储示意图如下:
str5 = "World";
至此,str5的引用是对象“World”;str4和str3的引用是同一个对象“Hello”;str2和str1的引用是不同的对象“Hello”。
静态变量和Class对象究竟存放在哪个区域
Java8的静态变量存在于堆中,而不是元空间。
1、类加载时,.class文件被加载到内存中,并构建了一个Class对象,在堆中。
2、静态变量经过追踪,发现其和Class对象绑定在一起,由 1 可知其存在于堆中
(都说静态变量在方法区,只能说.class文件的信息都在方法区,所以静态变量的信息存在于方法区,静态变量本身并不在)
3、它的对象实体(new出来的对象)也存在于堆中(java8)。
补充:java8 将 静态变量 、StringTable 都从方法区移动到 堆中,主要是想进行GC,
因为方法区虽然也能GC,但只能full GC,频率会很低。