您的位置:首页 > 编程语言 > Java开发

由intern深入String的内存模型

2017-09-06 23:17 399 查看
引言

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


JDK6及之前版本,String Pool的实现



JDK7,String Pool的实现



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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java string intern pool