这是对我们生成的一些生成代码的精炼,现在我们切换到1.8会导致问题。

有人可以帮助解释为什么它可以在Java 1.6中编译并运行,但在1.8中导致内存不足错误吗?另外,如果您将set.add(s1)行注释掉,它似乎在1.8中可以正常运行。

我很确定不是因为我将5个字符的子字符串存储在集合中。它应该能够处理其中的12,000个。另外,即使我将行更改为set.add(new String(s1))set.add(s1 + " ")以尝试强制创建新字符串,它也可以在1.6中工作。

package put.your.package.here;

import java.util.HashSet;
import java.util.Set;

public class SubstringTest {

    public static void main(String[] args) {
        String s = buildArbitraryString();
        System.out.println(System.getProperty("java.version") + "::" + s.length());
        Set<String> set = new HashSet<String>();
        while (s.length() > 0) {
            s = whackString(s, set);
        }
    }

    private static String whackString(String s, Set<String> set) {
        String s1 = s.substring(0, 5);
        String s2 = s.substring(5);
        s = s2;
        set.add(s1);
        System.out.println(s1 + " :: " + set.size());
        return s;
    }

    private static String buildArbitraryString() {
        StringBuffer sb = new StringBuffer(60000);
        for (int i = 0; i < 15000; i++)
            sb.append(i);
        String s = sb.toString();
        return s;
    }
}

有任何想法吗?

JVM版本信息:
java.vm.name=IBM J9 VM
java.fullversion=
    JRE 1.8.0 IBM J9 2.8 Windows 7 amd64-64 Compressed References 20160210_289934 (JIT enabled, AOT enabled)
    J9VM - R28_Java8_SR2_20160210_1617_B289934
    JIT  - tr.r14.java_20151209_107110.04
    GC   - R28_Java8_SR2_20160210_1617_B289934_CMPRSS
    J9CL - 20160210_289934

编辑以添加JVM信息

最佳答案

好的,我们做了很多进一步的挖掘,我们认为已经找到了问题。
在WAS / IBM Java 1.6实现中,子字符串调用如下所示:

return ((beginIndex == 0) && (endIndex == count)) ? this :
    new String(offset + beginIndex, endIndex - beginIndex, value);

我们使用调试器对此进行了验证。每个新的String使用具有不同偏移量和计数的相同主数组。奇迹般有效。

在我们拥有的WAS / IBM Java 1.8版本中,子字符串调用如下所示:
if (!disableCopyInSubstring) {
    return new String (offset + start, end - start, value, false);
} else {
    return new String (offset + start, end - start, value);
}
disableCopyInSubstring标志始终为false,这很有意义。我们不想禁用将数据复制到新数组中的功能。该复制应该可以解决内存泄漏,该内存泄漏会重复使用同一char数组。这意味着substring调用以下构造函数(为简洁起见进行了编辑):
if (start == 0) {
    value = data;
} else {
    value = new char[length];
    System.arraycopy(data, start, value, 0, length);
}
offset = 0;
count = length;

因此,基本上,如果子字符串的开头为“0”,则它将保留整个原始char数组。由于某种原因,如果start为'0',则它忽略了修复内存泄漏的问题。故意。这是两全其美。

嗯是的。在我们的程序中,我们执行0-5子字符串,并且由于start为0时此实现不会创建新数组,因此它将存储整个巨型数组,计数长度为5。然后执行第二个子字符串,前5个字符。这确实为新的String创建了一个新的数组。然后,在下一个循环中,我们再次执行短子字符串,将整个巨型字符串的副本减去5个字符,然后再舍弃5个并制作一个新字符串。

我们一遍又一遍地去,每次都存储一个稍短的字符串的完整副本,只是消耗内存。

解决方案是用substring(0,5)包围new String()调用。我这样做了,在这个测试用例上它就像一个魅力。但是,我们正在处理生成的类,并且无法访问生成器,因此这不是我们的选择。

编辑:戴尔发现了这个
/**
 * When the System Property == true, then disable copying in String.substring (int) and
 * String.substring (int, int) methods whenever offset is non-zero. Otherwise, enable copy.
 */
String disableCopyInSubstringProperty = getProperty("java.lang.string.substring.nocopy"); //$NON-NLS-1$
String.disableCopyInSubstring = disableCopyInSubstringProperty != null &&
    disableCopyInSubstringProperty.equalsIgnoreCase("true"); //$NON-NLS-1$

10-02 00:03
查看更多