Java中几种常量池的区分 -->按此文章说法: 真实的字符串对象仍是在堆中,字符串常量池中存的是引用。git
常量池是为了不频繁的建立和销毁对象而影响系统性能,其实现了对象的共享。例如字符串常量池,在编译阶段就把全部的字符串文字放到一个常量池中github
(1)节省内存空间:常量池中全部相同的字符串常量被合并,只占用一个空间。缓存
(2)节省运行时间:比较字符串时,==比equals()快。对于两个引用变量,只用==判断引用是否相等,也就能够判断实际值是否相等安全
Class 文件常量池指的是编译生成的 class 字节码文件,其结构中有一项是常量池(Constant Pool Table),用于存放编译期生成的各类字面量(Literal)和符号引用(Symbolic References),这部份内容将在类加载后进入方法区的运行时常量池中存放。函数
运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放, 每个类加载到 JVM 中后对应一个运行时常量池。运行时常量池相对于 Class 文件常量池来讲具有动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池能够在运行期间将一部分符号引用解析为直接引用。好比说类的静态方法或私有方法,实例构造方法,父类方法,这是由于这些方法不能被重写其余版本,因此能在加载的时候就能够将符号引用转变为直接引用,而其余的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。将符号引用替换成直接引用,解析的过程会去查询全局字符串池StringTable,以保证运行时常量池所引用的字符串与全局常量池中的引用值保持一致。性能
能够说运行时常量池就是用来索引和查找字段和方法名称和描述符的,给定任意一个方法或字段的索引,经过这个索引最终可获得该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。this
字符串常量池是全局的,JVM 中独此一份,所以也称为全局字符串常量池。运行时常量池中的字符串字面量如果已经明确初始化赋值的成员变量,则在类的加载初始化阶段就使用到了字符串常量池;如果本地的,则在使用到的时候(执行此代码时)才会使用到字符串常量池。
经过stringTable维护,它是一个哈希表,里面存的是驻留字符串(也就是常说的用双引号括起来的)的引用(而不是驻留字符串实例自己),也就是说在堆中的某些字符串实例被这个StringTable引用以后就等同被赋予了”驻留字符串”的身份。这个StringTable在每一个HotSpot VM的实例只有一份,被全部的类共享。spa
字符串常量池实现的前提条件就是Java中String对象是不可变的,这样能够安全保证多个变量共享同一个对象。若是Java中的String对象可变的话,一个引用操做改变了对象的值,那么其余的变量也会受到影响,显然这样是不合理的。.net
“使用常量池”对应的字节码是一个 ldc <在编译文件中实锤>指令。在给 String 类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,如有就直接返回这个引用,若没有,就在堆里建立这个字符串对象并在字符串常量池中记录下这个引用(jdk1.7 +)。String 类的 intern() 方法还可在运行期间把字符串放到字符串常量池中。JVM 中除了字符串常量池,8种基本数据类型中除了两种浮点类型float、double, 剩余的6种基本数据类型的包装类,都使用了缓冲池技术,可是 Byte<[-128,127] >、Short<[-128,127] >、Integer<[-128,127] >、Long<[-128,127] >、Character<[0,127] > 这5种整型的包装类也只是在对应值在范围内 时才会使用缓冲池,超出此范围仍然会去建立新的对象。其中:
package com.noob.learn.netty; public class Main { private String s1 = new String("a"); private final String s2 = "b"; private final String s3 = "c" + "d"; private String s4 = s1 + s2; private String s5 = s3 + s1; private String s6 = s3 + s2; private final String s10 = new String("z") + s2; private final String s11 = new String("m"); private String s12 = s11 + s3; public static void test(String args) { String s5 = new String("e"); final String s6 = "f"; final String s7 = "g" + "h"; String s8 = s6 + s7; String s9 = s6 + "i"; String s10 = new String("j") + "i"; } private void get() { String s15 = new String("q"); String s16 = new String("p"); } public void get2() { String s15 = new String("x"); String s16 = new String("z"); } public static void main(String[] args) { } }
经过: javap -verbose Main.class 反编译
在本文以下事例中,该反编译文件显示出的内容含: 类文件的描述信息<类名、更新时间、版本>、类文件常量池<符号引用、字面量等>、方法信息(除private修饰外)。
方法:每个方法包含四个区域,
- 签名和访问标签
- 字节码
- LineNumberTable:为调试器提供源码中的每一行对应的字节码信息。例子中,Java 源码里的第 26 行与 get 函数字节码序号 0 相关,第 27 行与字节码序号 3 相关。
- LocalVariableTable:列出了全部栈帧中的局部变量。方法是非静态时,有局部变量 this。
结论:
方法体内和成员变量同时适用如下规则。 类文件常量池在类加载入运行常量池,建立对象入字符串常量池
一、文本字符串在编译期间入类文件常量池: “a”, "b","z", "m","e","f","j","i";
二、常量字符串的“+”操做,编译阶段直接会合成为一个字符串再入类文件常量池,单独的字符不入 : "cd",gh”;
三、 对于final的常量字符串(注意:必定要是文本字符串,new的都不能),编译直接进行了常量替换。入 类文件常量池: s6 -> 'cdb' ; s8 -> 'fgh' ; s9 -> 'fi';
四、'ldc'、'new' 指令出如今方法体内部