聊聊各种常量池
学习JVM的时候经常会遇到各种常量池,不同版本的JDK它们的存储位置也不同,这篇随笔就整理下几种常见的常量池,以JDK1.8为主。先看一张存储示意图,里面涉及1.8和1.6。
public class Demo { public static void main(String[] args) { System.out.println("hello world"); } }
我们对Demo.class“进行反汇编得到具体字节码信息,前面这一段是类的元信息,包括类名、修改时间、访问修饰关键字等。
接着我们可以看到 Constant pool:,这就是常量池,每一个符号后面引用了其他符号或者表示具体的信息。其实常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息。
运行时常量池:是方法区的一部分,JDK1.8方法区位于系统内存中。当类被加载到内存时,那么原先的常量池信息就会放入运行时常量池中,并且将 #1 这些符号地址变为直接引用。
字符串常量池
public class Demo { public static void main(String[] args) { String s1 = "a"; String s2 = "b"; String s3 = "ab"; String s4 = s1 + s2; System.out.println(s3 == s4); // false String s5 = "a" + "b"; System.out.println(s3 == s5); // true } }
编译这段程序的时候a、b、ab这些都是运行时常量池中的符号,还没变成Java 字符串对象。当执行到“String s1 = “a”; ”这行代码时,ldc会将a符号变为 “a” 字符串对象,如果串池中没有”a”对象,“a”对象就会被放入StringTable里。s2、s3逻辑类似,最终串池里面的对象为[“a”, “b”, “ab”]。
再看s5将两个字符串常量进行拼接,这时候JVM并不会像拼接变量那样创建对象,而是直接到串池中找到对象 “ab”,所以 “System.out.println(s3 == s5);” 输出值为true。这是因为 javac 在编译期间认为 “a” 、”b”是定值不会再改变, 所以直接得到结果“ab”。
其实我们可以使用 intern方法,主动将串池中还没有的字符串对象放入串池。
但是如果在程序开始时就将“ab”放入串池,再比较 s == “ab“ 就会返回false。