深入理解Java虚拟机案例代码P63代码

首先给出代码

package com.lsh.java.stackoverflow;

import java.util.HashSet;
import java.util.Set;

/**
 * 运行时常量池导致的内存溢出异常
 * 由于在JDK7以后,字符串常量池从方法区移动到了Java堆区,
 * 因此我们需要显示Java堆区的最大容量便可以很轻易的让程序出现OOM
 *
 * -Xmx6M
 * OutOfMemoryError:GC overhead limit exceeded
 */
public class RuntimeConstantPoolOOM {
//    public static void main(String[] args) {
//        Set<String> set = new HashSet<String>();
//        short i = 0;
//        while(true){
//            set.add(String.valueOf(i++).intern());
//        }
//    }
public static void main(String[] args) {
    String str1 = new StringBuilder("计算机").append("软件").toString();
    System.out.println(str1.intern() == str1);


    String str3 = "java";
    //创建str3 == java,那么字符串常量池中已经有java,那么str2创建的java字符串在Java堆内存中
    //自然,判断str2 == str3的话,地址不同,显然为false
    String str2 = new StringBuilder("ja").append("va").toString();
    //System.out.println(str3==str2);
    //由于java字符串已经在字符串常量池中有引用,不符合intern方法的“首次遇到”原则,因此判断也是false
    //如果注释掉String str3 = "java";  那么判断就会返回true,因为字符串常量池中并没有java,所以str2.intern方法会返回一个引用
    // 这个引用与str2相同,因此返回true
    System.out.println(str2.intern() == str2);
}
}

方法的解释在代码注释中已经写的很清晰了,在这里再做以下总结

  • 首先,本次环境是JDK8,书上给出案例是7
  • 书上给出代码与本地实现有差别,具体差别表现在
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
  • 书上给出的答案为 false ,原因如下
    • 因为”Java”这个字符串在执行String-Builder-toString()的时候就已经出现过了,那么字符串常量池就有他的引用
    • 而对于intern方法,如果在常量池中没有字符串的引用,那么就生成一个在常量池中的引用,相反,则不生成,生成的引用和堆中的对象地址相同
    • 在这里已经有java的引用了,不符合intern方法要求首次遇到的原则,那么判断自然会返回false
  • 注意,书上的环境为JDK7,java在加载sun.misc.Version这个类的时候进入常量池
  • 现在,在JDK8的环境下,以上代码运行结果为true,原因是在JDK8中并没有把Java加载进入字符串常量池
  • 那么我们必须要在程序的开头加上一句String str3 = "java"; 这样我们就定义了一个字符串 java进入常量池,这样intern就不会返回和堆中对象地址相同的引用,自然也会返回false