由intern深入String的内存模型

来源:互联网 发布:微信app支付 php demo 编辑:程序博客网 时间:2024/05/17 00:18

引言
Java里使用String的常量池(string pool),是对效率的妥协,是JVM中所模拟的缓存技术。

一、如何将String对象存储到常量池中:
1. 使用字面量常量声明(eg. String str = “Yeah”);
2. 使用intern进行赋值(JDK6-)/映射(JDK7+);
3. 利用编译器的自动优化(eg. String str = “Yeah” + “Man” //将YeahMan置入String pool);

二、String的不可变(immutable)性的探究:
在《Core Java》中对不可变字符串的描述是这样:“不可变字符串却有一个优点:编译器可以让字符串共享。Java的设计者认为共享带来的高效率远远胜过于提取、拼接字符串所带来的低效率。”
关于String的不可变性,是由于API中未对外提供相应的更改方法,使value的值恒定不可变。但我们可通过Java的动态反射机制,打破这种不可变性。

/**     * 通过动态反射改变String的value值     *     * @throws NoSuchFieldException     * @throws IllegalAccessException     */    private static void modifyStringValue() throws NoSuchFieldException, IllegalAccessException {        String oriStr = new String("Hello");        System.out.println("oriStr store address: " + Integer.toHexString(System.identityHashCode(oriStr)));        System.out.println(oriStr);        Field field = String.class.getDeclaredField("value");        field.setAccessible(true);        field.set(oriStr, "World".toCharArray());        System.out.println("newStr store address: " + Integer.toHexString(System.identityHashCode(oriStr)));        System.out.println(oriStr);    }

三、String.intern()所带来的变革(JDK6、JDK7):
String的常量池(String Pool)是存储在JVM内存中的,在JDK6之前(包含6),这一内存区域称为PermGen space(Permanent Generation space),JDK7则讲String Pool放在Heap中,JDK8进而一步移除了PermGen space 增设MetaSpace。以下将详细说明:

/**     * 字符串常量池在JDK6/7中的区别     * output:     *      JDK6:   false;     *      JDK7:   true;     */    public void stringPoolDiff() {        // 等价于 String Hello = "Hello"; String world = "World"; String s1 = Hello + world;        // 会被编译成 s1 = new StringBuilder().append(new String("Hello")).append(new String()).toString();        String s1 = new String("Hello") + new String("World");        s1.intern();        //jdk7 string pool中存储s1的引用        String s2 = "HelloWorld";        System.out.println(s1 == s2);    }
  1. JDK6及之前版本,String Pool的实现

JDK6 String Pool

  1. JDK7,String Pool的实现
    JDK7 String Pool

    1. JDK中通用实现,以JDK7为内存模型作图
/**     * 字符串常量池与堆空间     * output:     *      s1 storeAdr: 73c6c3b2     *      s2 storeAdr: 48533e64     *      s1_stringPool store address: 48533e64     */    @Test    public void poolAndHeapStore() {        String s1 = new String("Hello");    // string pool未存在“Hello”,先将“Hello”置入String pool,再进行对象创建        String s2 = "Hello";    // string pool已存在“Hello”,直接引用        String s1_stringPool = s1.intern();        System.out.println("s1 storeAdr: " + Integer.toHexString(System.identityHashCode(s1)));        System.out.println("s2 storeAdr: " + Integer.toHexString(System.identityHashCode(s2)));        System.out.println("s1_stringPool store address: " + Integer.toHexString(System.identityHashCode(s1_stringPool)));    }
/**     * 编译器自动优化字符字面量     * output:     *      true     *      true     *      false     */    @Test   public void compileAutoOptimize() {        // 编译器自动优化,“Hello”和“World”不存入string pool,直接置入“HelloWorld”        String s1 = "Hello" + "World";        String s2 = "HelloWorld";        // 验证"Hello"并未置入string pool        String s3 = new String("Hel") + new String("lo");        s3.intern();        String s4 = "Hello";        // 只有字符常量拼接,编译器才进行优化        String s5 = "Hello".concat("World");        System.out.println(s1 == s2);        System.out.println(s3 == s4);        System.out.println(s2 == s5);    }
/**     * 常量字符串编译期才放入string pool     * output:     *      false     *      true     */    @Test    public void poolAndHeapStore6() {        // 常量字符串在编译期才放入string pool,s2通过s1而得到,属于运行时赋值,存于堆中;        String s1 = "Hello";        String s2 = s1 + "";        String s3 = "Hello" + "";        System.out.println(s1 == s2);        System.out.println(s1 == s3);    }
原创粉丝点击