Это дистилляция некоторого сгенерированного кода, который у нас есть, который вызывает проблемы теперь, когда мы перешли на 1.8.
Может ли кто-нибудь помочь объяснить, почему это компилируется и работает в Java 1.6, но вызывает ошибку нехватки памяти в 1.8? Кроме того, кажется, что в 1.8 он работает нормально, если вы закомментируете строку set.add(s1)
.
Я почти уверен, что это не потому, что я храню 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
2 ответа
Хорошо, мы проделали еще большую работу и думаем, что нашли проблему. В реализации WAS / IBM Java 1.6 вызов подстроки выглядит так:
return ((beginIndex == 0) && (endIndex == count)) ? this :
new String(offset + beginIndex, endIndex - beginIndex, value);
Мы проверили это с помощью отладчика. Каждая новая строка использует один и тот же основной массив с разными смещениями и счетчиками. Работает как шарм.
В имеющейся у нас версии 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
всегда ложный, что имеет смысл. Мы не хотим отключать копирование данных в новый массив. Это копирование должно исправить утечку памяти, которая вызывает многократное использование одного и того же массива char. Это означает, что substring
вызывает следующий конструктор (отредактированный для краткости):
if (start == 0) {
value = data;
} else {
value = new char[length];
System.arraycopy(data, start, value, 0, length);
}
offset = 0;
count = length;
Итак, в основном, если начало подстроки равно «0», сохраняется весь исходный массив символов . По какой-то причине, если start
равен '0', утечка памяти не устраняется. Нарочно. Это худшее из обоих миров.
Так что да. В нашей программе мы используем подстроку 0-5, и поскольку эта реализация не создает новый массив, когда start
равно 0, она хранит весь гигантский массив с длиной счета 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$
.substring(0)
просто копирует строку. Цитата: По какой-то причине, если начало - "0", утечка памяти не устраняется. В 1.8 утечки памяти нет, если вы хотите, с 0-й позиции до конца строки вы просто получите ту же строку обратно.
У меня нет полного ответа, но я не могу комментировать, потому что у меня недостаточно кредитов для этого.
Вы должны прочитать ответ в следующем посте: метод подстроки в классе String вызывает утечку памяти
Это объясняет, что реализация подстроки изменилась. Я думаю, вам следует проверить влияние больших подстрок, возвращаемых методом wackString, и сборка мусора очищает их достаточно быстро, потому что они потребляют намного больше памяти из-за новой реализации подстроки.
Похожие вопросы
Новые вопросы
java
Java - это язык программирования высокого уровня. Используйте этот тег, если у вас возникли проблемы с использованием или пониманием самого языка. Этот тег редко используется отдельно и чаще всего используется вместе с [spring], [spring-boot], [jakarta-ee], [android], [javafx], [hadoop], [gradle] и [maven].
1.8
.-XX:+HeapDumpOnOutOfMemoryError
, затем попробуйте MAT на нем, я бы посоветовал .