20

短版:如果调用string.substring(n,m).intern(),字符串表是保留子字符串还是原始字符串?

...但我不确定这是一个正确的问题,所以这里是长版本:

我正在使用遗留 Java 代码 (PCGen),它通过将每个文件作为一个大字符串来解析文件,然后使用 String.split、.trim、.substring 和 StringTokenizer 将它们分解为令牌。这对于解析非常有效,因为这些方法都不会复制原始字符串,而是都指向共享 char[] 的一部分。

解析结束后,我想回收一些内存。只需要原始大字符串的几个小子字符串,但强引用阻止了大字符串被收集。后来我遭受了OOM,我相信部分是由于大量已解析文件的巨大堆影响。

new String(String)我知道我可以通过(写时复制)修剪大字符串。而且我知道我可以通过 String.intern 减少字符串重复(这很重要,因为解析的文件中有很多冗余)。我需要同时使用两者来回收最大数量的堆,还是 .intern() 两者都做?阅读 OpenJDK7 热点源代码(hotspot/src/share/vm/classfile/symbolTable.cpp),看起来字符串表保留了整个字符串,并且根本没有修剪它的偏移量/长度。所以我想我需要制作一个新的字符串,然后实习这个结果。对?

综上所述,切换到流式解析器在内存方面将是一个巨大的胜利,但这对于短期而言变化太大了。

4

2 回答 2

10

您可以使用 new String(String) 和 intern() 方法,这将根据需要获取 Java 7 update 4 的副本。从 Java 7 update 5 开始,子字符串将获取更深的副本,但您可能仍想使用 intern ()。注意:Java 7 使用堆而不是 perm gen 来存储字符串文字。

public static void main(String[] args) {
    char[] chars = new char[128];
    Arrays.fill(chars, 'A');
    String a128 = new String(chars);
    printValueFor("a128", a128);
    String a16 = a128.substring(0, 16);
    printValueFor("a16", a16);
}

public static void printValueFor(String desc, String s) {
    try {
        Field value = String.class.getDeclaredField("value");
        value.setAccessible(true);
        char[] valueArr = (char[]) value.get(s);
        System.out.println(desc + ": " + Integer.toHexString(System.identityHashCode(valueArr)) + ", len=" + valueArr.length);
    } catch (Exception e) {
        throw new AssertionError(e);
    }
}

在 Java 7 更新 4 打印

a128: 513e86ec, len=128
a16: 53281264, len=16

我希望 Java 6 不会这样做。

于 2013-01-25T06:48:15.150 回答
3

我们可以测试一下。字符串将其字符数组保存在一个字段中

   private final char value[];

让我们看看在 substring() 之后会发生什么;实习生();

    Field f = String.class.getDeclaredField("value");
    f.setAccessible(true);
    String s1 = "12345";
    String s2 = s1.substring(1, 2);
    String s3 = s2.intern();
    System.out.println(f.get(s2) == f.get(s1));
    System.out.println(f.get(s3) == f.get(s2));

输出

true
true

也就是说,所有 3 个字符串共享同一个字符数组

于 2013-01-25T06:59:14.553 回答