[英]Java substring operation seems to cause Out of Memory Error in java 1.8
這是我們已經切換到1.8的一些生成的代碼的升級,這些代碼導致了問題。
有人可以幫助解釋為什么在Java 1.6中編譯和運行,但在1.8中導致內存不足錯誤? 此外,如果你注釋掉set.add(s1)
行,它似乎在1.8中運行良好。
我很確定這不是因為我將5個字符的子串存儲在一個集合中。 它應該能夠處理其中的12,000個。 另外,它在1.6中工作,即使我將行更改為set.add(new String(s1))
或set.add(s1 + " ")
以嘗試強制創建新字符串。
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創建了一個新數組。 然后,在下一個循環中,我們再次執行短子串,制作整個巨型字符串減去五個字符的副本,然后我們再關閉五個並創建一個新字符串。
我們一遍又一遍地,每次都存儲一個稍短的字符串的完整副本,只是咀嚼內存。
解決方案是用new String()
包圍substring(0,5)
調用。 我這樣做了,它就像這個測試用例的魅力一樣。 但我們正在處理一個生成的類,我們無法訪問生成器,所以這不是我們的選擇。
編輯:戴爾發現了這一點
/**
* 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$
我沒有完整的答案,但我無法評論,因為我沒有足夠的學分。
您應該閱讀以下帖子中的答案: String類中的substring方法導致內存泄漏
它解釋了子串的實現發生了變化。 我認為你應該檢查方法wackString返回的大子串的影響,而且垃圾收集很快就能清除它們因為子串的新實現會消耗更多的內存。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.