通过子字符串解析后如何回收内存?intern() 还是 new String()?

2022-09-02 10:46:39

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

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

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

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

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

总而言之,切换到流式解析器在内存方面将是一个巨大的胜利,但这在短期内是一个太大的变化。


答案 1

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

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不会这样做。


答案 2

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

   private final char value[];

让我们看看子字符串()之后会发生什么;实习生();

    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 个字符串共享相同的字符数组