Java字符串存储在哪里

早上看群友们在讨论,出现了Java字符串究竟在哪里的话题,粗略目睹虚拟机的菜鸡本人起了兴趣,研究了一下整理出来这个博文

字符串究竟存在哪里

这里总结了大家同意的说法:

  • 如果使用常量的方式,该对象将被存储在常量池(永久代)
  • 如果使用new的方式,该对象将被存储在堆

下面的代码揭示了情况:

  • 使用常量的形式,为同一个对象
  • 使用new的方式,为不同的对象
  • 即使调用了常量创建的对象的方法,该对象也并不会发生什么变化:说明就是一个对象,而不是一组数组
public class TestString {
    
    
    public static void main(String[] args) {
    
    
        String a="Aa";
        String b="Aa";
        System.out.println(a==b);
        String c=new String("Aa");
        System.out.println(b==c);
        b.charAt(0);
        String d=new String("Aa");
        System.out.println(d==c);
        System.out.println(b==a);

    }
}
/**
true
false
false
true

Process finished with exit code 0
**/

简单的规则就是这样了,希望浅显了解看到这里就足够了
但反观具体的实现,又会觉得奇妙,比如Java的字符串常量池,intern方法,下面详细说明

更深入一步的了解

在开始之前,各位先看这个String中神奇的方法
String.intem();

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java&trade; Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */  
public native String intern(); 

这个方法是一个 native 的方法,但注释写的非常明了。“如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回”。

先记住这个奇妙的方法,建议你思考一下这个逻辑。

然后了解一下JDK1.6与JDK1.7中常量池的区别

先看看JDK1.6

JDK1.6中常量池是一块独立的区域,jdk6中的常量池是放在 Perm 区中的,Perm 区和正常的 JAVA Heap 区域是完全分开的。
有点人可能不是很懂
这里再换种说法:
“String”类型的String,存放在常量池,同时常量池与Java堆是两个不同的部分
也就是说,

  • 存放在常量池和存放在堆,实现方式都不一样。
  • 常量池默认4M,所以存在消耗尽,抛出java.lang.OutOfMemoryError: PermGen space

在这里插入图片描述
来到JDK1.7
在JDK1.7中,取消了这个称为perm的区域,常量池就是堆,在常量池中的String对象和堆的String对象是存在与同一区域的。在 jdk7 的版本中,字符串常量池已经从 Perm 区移到正常的 Java Heap 区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称 jdk8 已经直接取消了 Perm 区域,而新建立了一个元区域。应该是 jdk 开发者认为 Perm 区域已经不适合现在 JAVA 的发展了。
在这里插入图片描述

问题发现

综合上述,不难发现,在JDK1.6和JDK1.7中运行同样的代码,结果缺会有不同

public static void main(String[] args) {
    
    
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

在JDK1.6中,常量池和堆是明显分隔的
于是我们可以推断:

  • s对象new出,于是在堆中
  • s2对象为常量,存在与常量池
  • s与s2存储的位置都不同,s!=s2
  • s3为两个字符串对象相加,调用了intern方法,但是存储位置都不同,s3必然不同于s4

结果为false false

在JDK1.7中,由于不存在了perm这个特殊的区域

  • s对象new出,于是在堆中
  • s2对象为常量,存在与常量池
  • s与s2存储的位置都不同,s!=s2
  • s3对象值为“11”,但是是由两个"1"拼接,所以常量池中只会存在"1"而不会存在"11"(这点很重要)
  • s3intern方法,在常量池中生成了"11"。因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 “11” 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。
  • s4直接使用常量池"11"
  • s3==s4

参考文章
https://www.baeldung.com/java-string-pool
https://www.cnblogs.com/holten/p/5782596.html
https://www.baeldung.com/java-string-pool

猜你喜欢

转载自blog.csdn.net/weixin_44494373/article/details/108660015
今日推荐