目录
常量池与运行时常量池
字符串常量池String_Table
字符串变量拼接
字符串常量拼接
字符串延迟加载
字符串intern方法
总结StringTable的特点
常量池与运行时常量池
二进制字节码包括 类的基本信息,常量池,类方法定义(包含虚拟机指令)
class文件中除了有类的版本,字段,方法,接口等描述信息外,还有用于存放编译器生成的各种字面量和符号引用的 常量池表.
字面量是源代码中的固定值的表示法,即通过字面我们就能知道其值的含义。
字面量包括整数、浮点数和字符串字面量.
符号引用包括类符号引用、字段符号引用、方法符号引用和接口方法符号引用。
常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息.
常量池表会在类加载后存放到方法区的运行时常量池中.
运行时常量池的功能类似于传统编程语言的符号表,尽管它包含了比典型符号表更广泛的数据。
既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 错误。
运行时常量池 : 常量池 是*.class文件中的,当该类被加载(运行的时候,放到内存中),它的常量池信息就会放入方法区的运行时常量池,并把里面的符号地址变为真实的地址
字符串常量池String_Table
String s1 = "a"; String s2 = "b"; String s3 = "a" + "b"; String s4 = s1 + s2; String s5 = "ab"; String s6 = s4.intern(); // 问 System.out.println(s3 == s4); System.out.println(s3 == s5); System.out.println(s3 == s6); String x2 = new String("c") + new String("d"); String x1 = "cd"; x2.intern(); // 问,如果调换了【最后两行代码】的位置呢,如果是jdk1.6呢 System.out.println(x1 == x2);
常量池中的信息都会被加载到运行时常量池中,此时 这些常量都是常量池中的符号,还没有变为Java字符串对象. 当执行了ldc 会把符号变为Java对象(字符串对象),同时也会准备一个StringTable(串池HashTable),变为Java对象之后(字符串对象)把它作为key,就会看StringTable里面有没有这个字符串,如果没有就会把它放入到串池中--->懒加载(执行到某行代码才创建对象,如果池中没有该字符串对象就放到池子中,如果串池中有,就会用串池中的对象,每一个字符串对象在串池中都是唯一的.)
字符串变量拼接
将字节码进行反编译可以看出.
String s1 = "a"; String s2 = "b"; String s3 = s1 + s2; // new StringBuilder().append("a").append("b").toString()
字符串变量拼接,其实会转化为StringBuilder的拼接,然后在调用StringBuilder的toString方法.
而StringBuilder的toString方法,又是将拼接好的值又创建了一个String对象.-->新的字符串对象
原因是s3创建的字符串对象会在StringTable(串池)里面,而s4是StringBuilder拼接好的值有创建了一个新的String对象.两者的位置不同. s3的字符串对象是存在串池中的,而s4的字符串对象是存在堆中的
字符串常量拼接
字符串常量拼接由于是常量拼接,常量不会被修改,所以在Javac在编译期间就会进行优化,在编译器就会拼接好,在编译器就已经确定;
字符串延迟加载
字符串对象不是一下子全放到串池的,而是执行到哪一行代码,再把它放入到串池,是懒加载的行为.
当再次执行代码发现这个字符串已经在串池中出现了,那么就不会再去创建,而是沿用串池(StringTable)中的字符串
字符串对象是延迟加载的-->懒惰行为.
字符串intern方法
String s = new String("a") + new String("b");
- 对于这一行代码,首先有常量 "a",把它放入到串池中,有常量 "b"把它放入到串池中.
- 然后又new String("a"),创建出一个新的对象,它是在堆中,只是与串池中的"a"值是相同的.
- 然后又new String("b"),创建出一个新的对象,也是在堆中,只是与串池中的 "b"值是相同的
- 然后又将new String("a")和new String("b")拼接起来,原理其实是StringBuilder的拼接然后重新创建出一个对象在堆中
String s = new String("a") + new String("b"); String s2 = s.intern();
调用intern方法,主动将串池中还没有的字符串对象放入串池, 如果字符串常量池中存在该字符串对象,那么不放入,如果常量池中不存在该字符串对象,则将这个字符串对象放入到串池中. 调用intern方法最终返回的是串池中的字符串对象(JDK1.8版本)
总结StringTable的特点
- 常量池中的字符串仅仅是符号,第一次用到的时候才会转变为对象
- 利用串池机制,来避免重复创建字符串的对象
- 字符串变量的拼接是StringBuilder(Java 1.8)
- 字符串常量拼接的原理是编译器优化
- 可以使用intern方法,主动将串池中还没有的字符串对象放入串池
-
- JDK1.8调用intern方法,会尝试将字符串对象尝试放入到串池,如果有则不会放入到串池,如果没有放入到串池中。 调用intern方法最终返回的是串池中的对象。(调用intern方法的字符串对象和真正放入到串池中的对象都是串池中的对象是相同的对象)
- JDK1.6调用intern方法,会尝试将字符串对象尝试放入到串池,如果有,则不会放入到串池,如果没有,会把对象复制一份,放入到串池。调用intern方法最终返回的是串池中的对象。(调用intern方法的字符串对象和真正放入到串池中的对象是两个不同的对象)
- 总结一点就是JDK1.8调用intern如果串池中没有会直接放入到串池中,而对于JDK1.6来说它是复制一份放到串池中(所以调用intern方法的字符串对象和返回的对象不是同一个对象)