java学习——浅谈Java常量池

2018-07-02  本文已影响0人  高稷

一、概述

二、字符串常量池

字符串常量池属于运行时常量池的一部分

String s1 = "Hello";
String s2 = "Hello";
String s3 = "Hel" + "lo";
String s4 = "Hel" + new String("lo");
String s5 = new String("Hello");
String s6 = s5.intern();
String s7 = "H";
String s8 = "ello";
String s9 = s7 + s8;
          
System.out.println(s1 == s2);  // true
System.out.println(s1 == s3);  // true
System.out.println(s1 == s4);  // false
System.out.println(s1 == s9);  // false
System.out.println(s4 == s5);  // false
System.out.println(s1 == s6);  // true

java中==比较的是内存地址

  1. s1 == s2 (true),s1、s2赋值时均使用的字符串字面量"Hello",在编译期间,这种字面量会直接放入class文件的常量池中,载入运行时常量池后,s1、s2指向的是同一个内存地址。
  2. s1 == s3 (true),s3虽然是动态拼接出来的字符串,但所有参与拼接的部分都是已知的字面量,在编译期间,这种拼接会被优化,因此String s3 = "Hel" + "lo";在class文件中被优化成String s3 = "Hello";,所以s1 == s3成立。
  3. s1 == s4 (false),s4虽然也是拼接出来的,但new String("lo")这部分不是已知字面量,编译器不会优化,必须等到运行时才可以确定结果,所以s4指向堆中某个地址。


    s4.jpg
  4. s1 == s9 (false),s9是s7、s8两个变量拼接,都是不可预料的,编译器不作优化,运行时拼接成新字符串存于堆中某个地址。


    s9.png
  5. s4 == s5 (false),二者都在堆中,但地址不同。
  6. s1 == s6 (true),这两个相等完全归功于intern()方法,s5在堆中,内容为"Hello" ,intern方法会尝试将"Hello"字符串添加到常量池中,并返回其在常量池中的地址,因为常量池中已经有了"Hello"字符串,所以intern方法直接返回地址;而s1在编译期就已经指向常量池了,因此s1和s6指向同一地址,相等。

三个非常重要的结论:

  1. 必须要关注编译期的行为,才能更好的理解常量池。
  2. 运行时常量池中的常量,基本来源于各个class文件中的常量池。
  3. 程序运行时,除非手动向常量池中添加常量(比如调用intern方法),否则jvm不会自动添加常量到常量池。

三、常量池溢出

/**
 * jdk1.6 -XX:MaxPermSize=5M OutOfMemoryError: PermGen space
 * jdk1.7 -Xmx5M OutOfMemoryError: Java heap space
 * jdk1.8 -Xmx5M OutOfMemoryError: GC overhead limit exceeded
 * jdk1.8 -XX:MaxMetaspaceSize=2M OutOfMemoryError: Metaspace
 */
public class ConstantPoolOOM {

  public static void main(String[] args) throws InterruptedException {
    List<String> list = new ArrayList<String>();
    int index = 0;
    while (true) {
      list.add(String.valueOf(index++).intern());
    }
  }
}

四、jdk1.6和1.7+常量池的差异

/**
 * jdk1.6 false false
 * jdk1.7+ true false
 */
public class ConstantPoolTest {

  public static void main(String[] args) {
    String str1=new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern()==str1);
    String str2=new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern()==str2);
  }
}
  1. 上面代码在jdk1.6中执行会打印两个false
    1.6中intern()方法会把首次出现的字符串实例复制到运行时常量池(方法区)中,并返回方法区中这个实例的地址,而str1 str2的地址都在堆中,所以两次都打印false。
  2. 在jdk1.7种执行第一个会打印true,第二个会打印false
    1.7以后版本intern()方法会把首次出现的字符串实例的引用运行时常量池中,
    "计算机软件"是首次出现的字符串,会把str1的引用存入运行时常量池,所以str1.intern()返回的就是str1的引用,打印true。
    "java"在创建str2对象之前已出现过,运行时常量池中已经存在,所以打印false。

参考资料:
http://www.cnblogs.com/iyangyuan/p/4631696.html
https://segmentfault.com/a/1190000010412582

上一篇下一篇

猜你喜欢

热点阅读