【问题标题】:String Creation and char array Memory Allocation字符串创建和字符数组内存分配
【发布时间】:2013-04-15 15:53:14
【问题描述】:

我已经阅读了很多关于创建 String 时内存分配的冲突文章。 有的文章说 new 操作符在堆中创建一个字符串,而字符串字面量是在字符串池 [Heap] 中创建的,而有些文章说 new 操作符在堆中创建一个对象,在字符串池中创建另一个对象。

为了分析这一点,我编写了以下程序,它打印字符串字符数组和字符串对象的哈希码:

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 是如何创建的,如果 String 具有相同的值,那么它们共享相同的 char 数组。

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

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

【问题讨论】:

  • Java 编译器实在是太聪明了。试试"...".toCharArray() 之类的。但随后信息水平下降到零。
  • this 可能有用
  • 如果您从StringBuilder 构建String 会更有说服力,也许是通过调用单独的例程来附加部分字符串值。
  • @Anirudh:我读了那个链接,但它没有谈论字符串的内部字符数组。
  • @JoopEggen:没明白你的意思。能否请您详细说明一下。

标签: java string memory permgen


【解决方案1】:

从字符串源

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

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

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

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

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

【讨论】:

  • 仅供参考,您的源示例来自 Java 的早期版本(我猜是 1.5)。它导致了 很多 意外的内存异常,这就是当前 (1.6/1.7) 版本查看支持数组的大小与报告的字符串大小的原因。
  • @Evgeniy:这解释了为什么 char 数组对于使用 new 创建的所有字符串都是相同的,并且它与字符串文字共享它。有什么方法可以测试这个 char 数组是在堆还是 permgen 中创建的?
  • 如果 str == str.intern() 这意味着 str 在 permgen 中
  • @Evgeniy:str == str.intern() 会告诉我有关字符串对象引用的信息,但不会告诉我有关内部字符数组的信息。
  • 我同意,但是 1) 至少如果 String 在 permgen 中,那么它的 char 数组也可以保证在那里 2) 几乎没有其他方法
【解决方案2】:

从这个输出中可以清楚地看出,无论 String 是如何创建的,如果 String 具有相同的值,那么它们共享相同的 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

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

现在我的问题是这个字符数组存储在哪里

据我所知,字符串文字的字符数组存储在堆中(那些对类加载内部有更好了解的人,请随时发表评论)。从文件加载的字符串将总是将它们的后备数组存储在堆上。

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

【讨论】:

  • 我已经在 J​​LS 中检查过字符串对象,无论是文字还是新字符串都确实存储在堆中。字符串池只是一组引用。所以 char 数组确实进入了堆。
【解决方案3】:

Last first:根据定义,字面量“myTestString”是interned,所有具有相同值的interned String 引用都指向同一个物理String 对象。因此,文字将与 intern 的结果完全相同。

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

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

(更多信息:String 的旧实现包括指向 char[] 的指针、偏移量、长度和 hashCode 值。较新的实现不推荐使用偏移值,String 值从数组的元素 0 开始. 其他(非 Sun/非 Oracle)实现取消了单独的 char[] 数组,并将字符串字节包含在主堆分配中。不要求 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

【讨论】:

  • “根据定义,String 上的 String.hashCode 和 System.identityHashCode 返回相同的值”——您对此有参考吗?因为这肯定不是docs 所说的。
  • @parsifal - 好的,你让我明白了 - 稍微误读了规范。 identityHashCode 可能返回哈希的“混乱地址”版本,因此会识别不同(但“相同”)的对象。
  • @HotLicks:如果您看到输出,则字符数组的哈希码对于所有字符串都是相同的。所以这是不正确的“所以前两个数组哈希不同也就不足为奇了”
猜你喜欢
  • 1970-01-01
  • 2016-03-25
  • 2022-01-17
  • 1970-01-01
  • 1970-01-01
  • 2014-01-05
  • 1970-01-01
  • 2011-03-22
  • 1970-01-01
相关资源
最近更新 更多