从String.intern()看出JMM变迁史

Java中使用的常量池,是JVM提供的缓存机制。

一. 如何将String对象存储到常量池中:
  1. 直接使用双引号声明出来的String对象会直接存储在常量池中。
  2. 使用intern方法,若常量池中不存在,则置入。
  3. 利用编译器的自动优化(eg. String str = “Hey” + “Man” //将HeyMan置入String pool);
二. String常量池的底层实现

String Pool是一个固定大小的Hashtable,默认长度为1009,若Hash冲突严重,导致链表过长,性能大幅下降,且不可自动扩容(使用jni调用C++的 StringTable::intern 方法)。

JDK7+可通过配置-XX: StringTableSize=, 设置初始长度。

三. Perm Space的变更
  • 从一个经典面试题入手:String s = new String("abc")创建了几个对象?分别创建了两个对象,第一个对象“abc”存储在常量池中,第二个对象在Java Heap中的String对象。

  • JDK7后将字符串常量池从Perm区移到heap区域,导致了intern方法,发生巨大变化,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。
    1. 从以下例子,可以清晰看出变化。

      /**
           * 字符串常量池在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);
          }
      

JDK6 String Pool
JDK7 String Pool

可以从图中看出,JDK7中,intern()方法,往String Pool存储的是一个堆中对象的引用。
四. String中易陷入的坑(JDK7中的内存模型)
  • new String(“Hello”),创建两个对象,分别存在String Pool和Heap中,intern()时,如果String Pool中已存在,则返回该引用。
/**
     * 字符串常量池与堆空间
     * output:
     *      s1 storeAdr: 73c6c3b2
     *      s2 storeAdr: 48533e64
     *      s1_stringPool store address: 48533e64
     */
    @Test
    public void poolAndHeapStore() {
        // string pool未存在“Hello”,先将“Hello”置入String Pool,再进行对象创建
        String s1 = new String("Hello");    
        System.out.println("s1 storeAdr: " + Integer.toHexString(System.identityHashCode(s1)));
        
        String s2 = "Hello";    // String Pool已存在“Hello”,直接引用
        System.out.println("s2 storeAdr: " + Integer.toHexString(System.identityHashCode(s2)));
        
        String s1_stringPool = s1.intern();
        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";
        System.out.println(s1 == s2);
        // 验证"Hello"并未置入String Pool
        String s3 = new String("Hel") + new String("lo");
        s3.intern();
        String s4 = "Hello";
        System.out.println(s3 == s4);
        // 只有字符常量拼接,编译器才进行优化
        String s5 = "Hello".concat("World");
        System.out.println(s2 == s5);
    }
  • String Pool只在编译期放入
/**
     * 常量字符串编译期才放入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);
    }

五. 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);
    }

祝近安
ooooor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值