字符串创建和字符串数组内存分配

时间:2013-04-22 16:41:11

标签: java string memory permgen

在创建String时,我已经阅读了很多关于内存分配的相互矛盾的文章。 有些文章说新的运算符在堆中创建一个String,而String literal是在String Pool [Heap]中创建的,而有些人说新运算符在堆中创建一个对象而另一个对象在String池中创建。

为了分析这个,我编写了下面的程序,它打印了String char数组和String对象的哈希码:

import java.lang.reflect.Field;

public class StringAnalysis {

    private int showInternalCharArrayHashCode(String s)
            throws SecurityException, NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException {
        final Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        return value.get(s).hashCode();
    }

    public void printStringAnalysis(String s) throws SecurityException,
            IllegalArgumentException, NoSuchFieldException,
            IllegalAccessException {
        System.out.println(showInternalCharArrayHashCode(s));

        System.out.println(System.identityHashCode(s));

    }

    public static void main(String args[]) throws SecurityException,
            IllegalArgumentException, NoSuchFieldException,
            IllegalAccessException, InterruptedException {
        StringAnalysis sa = new StringAnalysis();
        String s1 = new String("myTestString");
        String s2 = new String("myTestString");
        String s3 = s1.intern();
        String s4 = "myTestString";

        System.out.println("Analyse s1");
        sa.printStringAnalysis(s1);

        System.out.println("Analyse s2");
        sa.printStringAnalysis(s2);

        System.out.println("Analyse s3");
        sa.printStringAnalysis(s3);

        System.out.println("Analyse s4");
        sa.printStringAnalysis(s4);

    }

}

此程序打印以下输出:

Analyse s1
1569228633
778966024
Analyse s2
1569228633
1021653256
Analyse s3
1569228633
1794515827
Analyse s4
1569228633
1794515827

从这个输出中可以清楚地看出,无论String是如何创建的,如果Strings具有相同的值,那么它们共享相同的char数组。

现在我的问题是这个chararray存储在哪里,是存储在堆中还是存在于permgen?另外,我想了解如何在堆内存地址和permgen内存地址之间进行区分。

如果它存储在permgen中,我会遇到一个大问题,因为它会占用我珍贵的有限的permgen空间。如果char数组没有存储在permgen但存储在堆中,那么它是否意味着String文字也使用堆空间[这是我从未读过的东西]。

3 个答案:

答案 0 :(得分:3)

来自String src

 public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

很明显,使用此构造函数创建的字符串与原始字符串共享char数组(值)。

重要的是要注意API不保证这种共享:

初始化一个新创建的String对象,使其表示与参数相同的字符序列;换句话说,新创建的字符串是参数字符串的副本。除非需要显式的原始副本,否则不必使用此构造函数,因为字符串是不可变的

例如,String.substring用于与原始字符串共享char数组,但在最新版本的Java 1.7中,String.substring生成char数组的副本。

答案 1 :(得分:2)

  

从这个输出中可以清楚地看出,无论String是如何创建的,如果Strings具有相同的值,那么它们共享相同的char数组

不完全:这种情况正在发生,因为您从一个文字字符串开始,并从中创建多个实例。在OpenJDK(Sun / Oracle)实现中,如果支持数组表示整个字符串,则将复制该支持数组。您可以在src.jar或此处查看:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/lang/String.java#String.%3Cinit%3E%28java.lang.String%29

如果您仔细构造源字符串,使它们从不同的字符数组开始,您将发现它们不共享支持数组。

  

现在我的问题是这个chararray存储在哪里

据我所知,字符串文字的字符数组存储在堆上(那些具有更好的类加载内部的知识,随意评论)。从文件加载的字符串将始终将其支持数组存储在堆上。

我确实知道的是intern()使用的数据结构只引用String对象,而不是它的字符数组。

答案 2 :(得分:1)

最后一个:根据定义,文本“myTestString”被实现,并且具有相同值的所有实体String引用都引用相同的物理String对象。因此,intern的结果将是文字的精确相同。

[更正]根据定义,具有相同字符序列值的两个字符串的hashCode(但不是identityHashCode)将是相同的。

另一方面,char[]数组的hashCode只是其地址位的混乱,与数组的内容无关。这表明在上述所有情况下,value数组都是完全相同的数组。

(进一步信息:String的旧实现包括指向char[]的指针,偏移量,长度和hashCode值。较新的实现弃用偏移值,String值以元素0开头其他(非Sun /非Oracle)实现取消了单独的char[]数组,并在主堆分配中包含String字节。不要求value字段实际存在。)

[续]复制测试用例并添加几行。 hashCode和identityHashCode在给定的char[]上生成相同的值,但在具有相同内容的不同数组上生成不同的值。

在s1和s2中数组相同的事实几乎可以肯定是因为它们共享了实体文字“myTestString”的char[]数组。如果字符串是从“新鲜的”char[]数组中单独构建的,那么它们将是不同的。

所有这一切的主要内容是字符串文字被实现,并且当使用new String(String)复制字符串时,正在测试的实现“借用”源数组。

Char array hash codes
a1.hashCode() = 675303090
a2.hashCode() = 367959235
a1 identityHashCode = 675303090
a2 identityHashCode = 367959235
Strings from char arrays
a1 String = ABCDE
a1 String's hash = 62061635
a1 String value's identityHashCode = 510044439
a2 String = ABCDE
a2 String's hash = 62061635
a2 String value's identityHashCode = 1709651096