这是对我们生成的一些生成代码的精炼,现在我们切换到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$