JVM系统自学笔记2--类常量池、运行时常量池、字符串池

一、三者介绍

1、class文件常量池(class constant pool)

class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)

  • 字面量就是我们所说的常量概念,如文本字符串、被声明为final的常量值等。
  • 符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。
    • 类和接口的全限定名
    • 字段名称和描述符
    • 方法名称和描述符

2、运行时常量池:

  • jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。
  • 当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个
  • class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

3、全局字符串池(string pool也有叫做string literal pool)

  • 全局字符串池里的内容是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。)。
  • 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,不能扩容,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享

二、三者关系

  1. class常量池是java程序编译之后才有的,每个类都有,存放字面值和符号引用常量;
  2. 运行时常量池是在类加载完之后,常量池内容存储在运行时常量池中,每个类都有一个,且常量池中符号引用转换为直接引用,与全局字符串池中保持一致;
  3. 全局字符串池每个虚拟机只有一个,存储字符串常量的引用值;

例如:

String s1 = "a";

String s2 = "b";
String s3 = "ab";
/*
    new StringBuilder().append("a").append("b").toString();
    s1 s2 为变量,其值没有确定
*/
String s4 = s1+s2;
System.out.println(s4==s3);//false
/*
    javac编译期优化,“a”,“b”已经确定,所以在编译期s5就已经确定为“ab”
    在串池中寻找到“ab”,返回“ab”引用值
*/
String s5 = "a"+"b";
System.out.println(s3==s5);//true
  • 常量池中的都会被加载到运行时常量池中,此时字符串都是常量池中的符号,尚且未转化为对象
  • 运行时,将String字面量符号“a”转化为字符串对象,并在StringTable中寻找“a”,如多没有则添加“a”字符串对象。

三、StringTable特性

常量池中的字符串仅是符号,第一次用到时才便为对象;

利用串池的机制,来避免重复创建字符串对象;

字符串变量拼接的原理是StringBuilder;

字符串常量拼接的原理是编译期优化;

可以使用intern方法,主动将串池中还没有的字符串对象放入串池;

四、intern方法

jdk1.8:使用intern,如果串池中没有,则将变量入池,变量也变成指向串池的,如果有,则变量依旧不变,有/无都返回串池中的对象。

public class internDemo {
    public static void main(String[] args) {
        //new String("a") new String("b") new String("ab")
        //StringTable["a","b"] "ab"属于动态拼接,未加入StringTable
        String s = new String("a") + new String("b");

        //使用intern方法 在StringTable中放入此字符串对象,如果有则返回给字符串对象,没有在放入
        String s2 = s.intern();

        //s2指向的是串池中字符串对象 s也被放入串池中
        System.out.println(s==s2);//true
    }
}



public class internDemo {
    public static void main(String[] args) {
        String s3 = "ab";

        //new String("a") new String("b") new String("ab")
        //StringTable["a","b","ab"]

        String s = new String("a") + new String("b");
        //使用intern方法 在StringTable中放入此字符串对象,如果有则返回给字符串对象,没有在放入

        String s2 = s.intern();
        //s2指向的是串池中字符串对象 s未被放入串池,还在堆中

        System.out.println(s3==s2);//true
        System.out.println(s3==s);//false

    }
}

jdk1.6:使用intern,如果串池中没有,则将变量入池,变量不变成指向串池的,如果有,则变量依旧不变,有/无都返回串池中的对象。

public class internDemo {
    public static void main(String[] args) {

        //new String("a") new String("b") new String("ab")
        //StringTable["a","b"]
        String s = new String("a") + new String("b");
        //使用intern方法 在StringTable中放入此字符串对象,如果有则返回给字符串对象,没有则++复制一份后放入++
        String s2 = s.intern();
        //s2指向的是串池中字符串对象(s复制后的对象) ,s未被放入串池,还在堆中
	//StringTable["a","b","ab"]
 	String s3 = "ab";
        System.out.println(s3==s2);//true
        System.out.println(s3==s);//false

    }
}

五、StringTable位置

  • jdk1.6及之前,StringTable与方法区一同在永久代中。
  • jdk1.8之后,方法区转移到本地内存中,但是将StringTable转移到堆内存中。
  • 原因
    • StringTable中存在大量的字符串对象,运行时间增长永久代内存占用过多,且永久代只有在触发FULL GC时才进行垃圾回收,回收频率过慢。
    • 转移到堆中可以利用虚拟机在堆内存中频繁的垃圾回收,处理StringTable中对象过多情况。

六、StringTable调优

  • 调整hash表中桶子个数,-XX:StringTableSize=桶个数
发布了171 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/QilanAllen/article/details/105579521