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

Chr*_*lan 20 java memory

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

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

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

解析结束后,我想收回一些内存.只需要原始大字符串的几个小子串,但强引用可以防止收集大字符串.后来我遇到了OOM,我相信部分归因于大量解析文件的巨大堆影响.

我知道我可以通过new String(String)写入时复制大字符串(copy-on-write).我知道我可以通过String.intern减少字符串重复(这很重要,因为在解析的文件中有很多冗余).我是否需要同时使用两者来回收最大量的堆,或者.intern()是否同时执行这两种操作?读取OpenJDK7热点源代码(hotspot/src/share/vm/classfile/symbolTable.cpp),它看起来像字符串表保留整个字符串,并且根本不修剪偏移/长度.所以我想我需要创建一个新的String然后实习结果.对?

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

Pet*_*rey 10

您可以使用新的String(String)和intern()方法,这将根据Java 7更新4的需要进行复制.从Java 7更新5子字符串将采用更深的副本,但您可能仍希望使用实习生().注意: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);
    }
}
Run Code Online (Sandbox Code Playgroud)

在Java 7更新4打印

a128: 513e86ec, len=128
a16: 53281264, len=16
Run Code Online (Sandbox Code Playgroud)

我希望Java 6不会这样做.

  • @ChrisDolan:听起来它不仅仅是简化,而且可以证实是不正确的. (2认同)
  • 让Jon Skeet感到困惑的+1,这是一个非常新的事实 (2认同)
  • @ChrisDolan:从根本上说,我真的很想看到代码*在不同版本上运行* - 但我不打算开始创建虚拟机只是为了安装旧版本的Java;) (2认同)