Это дистилляция некоторого сгенерированного кода, который у нас есть, который вызывает проблемы теперь, когда мы перешли на 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

3
Risser 27 Июл 2017 в 22:37
Какую JVM вы используете? У меня он отлично работает на Hotspot VM. Также каковы ваши параметры запуска JVM? Обратите внимание, что некоторые параметры (например, конфигурации permgen) пропали в 1.8.
 – 
Adam Arold
27 Июл 2017 в 22:39
2
Откуда вы идете? stackoverflow.com/q/33893655/995891 будет иметь большое влияние.
 – 
zapl
27 Июл 2017 в 22:41
Пора запустить анализатор кучи.
 – 
Kevin Krumwiede
27 Июл 2017 в 22:43
1
Запустите JVM с переключателем -XX:+HeapDumpOnOutOfMemoryError, затем попробуйте MAT на нем, я бы посоветовал .
 – 
Adam Arold
27 Июл 2017 в 22:44

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$
3
Risser 28 Июл 2017 в 22:37
Вызов подстроки с одним параметром возвращается от этого индекса до конца строки. поэтому вызов .substring(0) просто копирует строку. Цитата: По какой-то причине, если начало - "0", утечка памяти не устраняется. В 1.8 утечки памяти нет, если вы хотите, с 0-й позиции до конца строки вы просто получите ту же строку обратно.
 – 
Andreas
28 Июл 2017 в 22:22
Да ладно, я не понял, о чем ты говоришь.
 – 
Andreas
28 Июл 2017 в 22:30
Установка следующего свойства системы решила проблему -Djava.lang.string.substring.nocopy = true. Также следует отметить, что это реализация Java от IBM.
 – 
Dale
28 Июл 2017 в 22:32

У меня нет полного ответа, но я не могу комментировать, потому что у меня недостаточно кредитов для этого.
Вы должны прочитать ответ в следующем посте: метод подстроки в классе String вызывает утечку памяти

Это объясняет, что реализация подстроки изменилась. Я думаю, вам следует проверить влияние больших подстрок, возвращаемых методом wackString, и сборка мусора очищает их достаточно быстро, потому что они потребляют намного больше памяти из-за новой реализации подстроки.

0
Stefan Mondelaers 27 Июл 2017 в 23:08