new String("123") String.intern() 内存区域分配和创建时机分析

近日和同事讨论到new String("123") 以及String.intern()的相关问题,这里做个简单记录。

直接进入分析阶段~

首先我们得了解jvm的内存结构,这里引用journaldev中的图

public class Memory {

    public static void main(String[] args) { // Line 1
        int i=1; // Line 2
        Object obj = new Object(); // Line 3
        Memory mem = new Memory(); // Line 4
        mem.foo(obj); // Line 5
    } // Line 9

    private void foo(Object param) { // Line 6
        String str = param.toString(); Line 7
        System.out.println(str);
    } // Line 8

}
 

这段代码在jvm内存分配如下(jdk1.7以后)

 

在jdk1.7之前串常量存储在方法区的PermGen Space。在jdk1.7之后,字符串常量重新被移到了堆中。字符串常量可以看做图中的String Pool 区。

简述:

String str = "123"; 这里str变量在栈中,指向String Pool 区的具体“123”对象。

String str2 = new String("abc");这句创建了几个对象呢?我们看下源码

/**
 * Initializes a newly created {@code String} object so that it represents
 * the same sequence of characters as the argument; in other words, the
 * newly created string is a copy of the argument string. Unless an
 * explicit copy of {@code original} is needed, use of this constructor is
 * unnecessary since Strings are immutable.
 *
 * @param  original
 *         A {@code String}
 */
public String(String original) {
    this.value = original.value;
    this.hash = original.hash;
}

 the newly created string is a copy of the argument string    这句翻译大意:“新创建的字符串是参数字符串的副本”

那么参数字符串是否创建就要视情况而定。

  • 如果“abc” 在String Pool 区中不存在,则会在String Pool中新建一个字符串对象,然后new String(“abc”) 又会在堆内存中创建一个对象。
  • 如果“abc”在String Pool区存在,则只会在堆中创建一个对象。

然后说下String.intern();这里intern()源码不贴出来了,里面关键语句翻译大意:“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。

《深入理解java虚拟机》写到:

因为JDK1.6中,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串的实例的引用,而StringBulder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。

在JDK1.7中,intern()的实现不会在复制实例,只是在常量池中记录首次出现的实例引用,因此返回的是引用和由StringBuilder.toString()创建的那个字符串实例是同一个。
 

 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:

  • 将String常量池 从 Perm 区移动到了 Java Heap区
  • String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象。

基于以上的的几点我们来几个测试验证上面论述:

String s = new String("1"); 
s.intern(); 
String s2 = "1";
System.out.println(s == s2);  //jdk1.6 false , jdk1.7 false


String s3 = new String("1") + new String("1"); 
s3.intern(); 
String s4 = "11"; 
System.out.println(s3 == s4); //jdk1.6 false , jdk1.7 true

这里针对jdk7之后内存模型进行解释: 

  • String s = new String("1");  首先会在常量池中创建"1"的对象,并且在堆中创建s的引用对象,s.intern() 会检查常量池中是否有s指向的对象值"1",由于常量池中已经有了,所有会返回常量池中的值(但是这里并没有用到返回值),String s2 = "1"; 这里s2指向常量池中的值,而s指向堆中的值,所有s == s2 为false;
  • String s3 = new String("1") + new String("1");  这里两个new String("1"); 并且"1"在常量池中是有的(此时常量池中并没有值"11"),s3 指向堆中对象的引用。
  • s3.intern() 首先会检查s3所对应的值"11" 是否在常量池中存在,由于常量池中并不存在,所有会把指向堆中字符串值"11"的引用s3 也存放一份到常量池中(即常量池中存放一份和s3相同指向的引用)(jdk6 会在常量池中生成一个 “11” 的对象)
  • String s4 = "11"; 这里会与常量池中做"equal"比较,由于常量池中已存在,所有s4指向常量池中的引用,即 s3 == s4

    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2); // jdk6 false, jdk7 false

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);  // jdk6 false, jdk7 false
  • 第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = "11";后了。这样,首先执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。
  • 第一段代码和第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。
String s = new String(“abc”);
String s1 = “abc”;
String s2 = new String(“abc”);
System.out.println(s == s1.intern()); //false
System.out.println(s == s2.intern()); //false
System.out.println(s1 == s2.intern()); // true
  • String s = new String(“abc”); 如果常量池不存在"abc"对象则会创建"abc",而s 指向堆中对象;
  • String s1 = "abc"; s1指向常量池中的对象,s1.intern() 返回常量池中的对象,所有s == s1.intern  // false。其余同理分析

以下是部分参考文章,感谢

https://tech.meituan.com/2014/03/06/in-depth-understanding-string-intern.html

https://www.journaldev.com/4098/java-heap-space-vs-stack-memory

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值