1. Введение

Одним из основных преимуществ Java является автоматизированное управление памятью с помощью встроенного сборщика мусора (или сокращенно GC). GC неявно заботится о выделении и освобождении памяти и, таким образом, способен решать большинство проблем, связанных с ее утечкой.

Хотя GC эффективно обрабатывает значительную часть памяти, он не гарантирует надежного решения проблемы с ее утечкой. GC достаточно умен, но не безупречен. Утечки памяти все еще могут закрасться даже в приложения, созданные добросовестным разработчиком.

По-прежнему возможны ситуации, когда приложение создает значительное количество лишних объектов, расходуя ресурсы памяти, что иногда приводит к его полному отказу.

Утечки памяти — это настоящая проблема в Java. В этом руководстве мы рассмотрим, каковы потенциальные причины утечек, как распознавать их в рантайме и как справиться с ними в нашем приложении.

2. Что такое утечка памяти

Утечка памяти — это ситуация, когда в куче присутствуют объекты, которые больше не используются, но сборщик мусора не может удалить их из памяти и, таким образом, они сохраняются там без необходимости.

Утечка памяти плоха тем, что она блокирует ресурсы памяти и со временем снижает производительность системы. Если с ней не бороться, приложение в конечном итоге исчерпает свои ресурсы и завершится с фатальной ошибкой java.lang.OutOfMemoryError.

Существует два различных типа объектов, которые находятся в Heap-памяти (куче) — со ссылками и без них. Объекты со ссылками — это те, на которые имеются активные ссылки внутри приложения, в то время как на другие нет таких ссылок.

Сборщик мусора периодически удаляет объекты без ссылок, но он никогда не собирает объекты, на которые все еще ссылаются. В таких случаях могут возникать утечки памяти:

Признаки утечки памяти

  • Серьезное снижение производительности при длительной непрерывной работе приложения

  • Ошибка кучи OutOfMemoryError в приложении

  • Спонтанные и странные сбои приложения

  • В приложении время от времени заканчиваются объекты подключения

Давайте подробнее рассмотрим несколько таких сценариев и как с ними бороться.

3. Типы утечек памяти в Java

В любом приложении утечка памяти может произойти по множеству причин. В этом разделе мы обсудим наиболее распространенные из них.

3.1. Утечка памяти через статические поля

Первый сценарий, который может привести к потенциальной утечке памяти, — это интенсивное использование статических переменных.

В Java статические поля имеют срок жизни, который обычно соответствует полному жизненному циклу запущенного приложения (за исключением случаев, когда ClassLoader получает право на сборку мусора).

Давайте создадим простую Java-программу, которая заполняет статический список:

public class StaticTest {
    public static List<Double> list = new ArrayList<>();

    public void populateList() {
        for (int i = 0; i < 10000000; i++) {
            list.add(Math.random());
        }
        Log.info("Debug Point 2");
    }

    public static void main(String[] args) {
        Log.info("Debug Point 1");
        new StaticTest().populateList();
        Log.info("Debug Point 3");
    }
}

Теперь, если мы проанализируем кучу во время выполнения этой программы, то увидим, что она увеличилась между точками отладки 1 и 2.

Но когда мы оставляем метод populateList() в точке отладки 3, куча еще не убрана сборщиком, как это видно в ответе VisualVM:

Однако в приведенной выше программе, в строке номер 2, если мы просто отбросим ключевое слово static, то это приведет к резкому изменению использования памяти, как показывает отклик:

Первая часть до точки отладки почти не отличается от того, что мы получили в случае static. Но на этот раз после выхода из метода populateList() вся память списка очищается, поскольку у нас нет на него ссылок.

Следовательно, нам нужно очень внимательно следить за использованием статических переменных. Если коллекции или большие объекты объявлены как статические, то они остаются в памяти на протяжении всего времени работы приложения, тем самым блокируя жизненно важную память, которую можно было бы использовать в другом месте.

Как предотвратить это?

  • Минимизируйте использование статических переменных

  • При использовании синглтонов полагайтесь на имплементацию, которая лениво, а не жадно загружает объект.

3.2. Через незакрытые ресурсы

Всякий раз, когда мы создаем новое соединение или открываем поток, JVM выделяет память для этих ресурсов. В качестве примера можно привести соединения с базой данных, входные потоки и объекты сессий.

Забыв закрыть эти ресурсы, можно заблокировать память, что сделает их недоступными для GC. Это может произойти даже в случае исключения, которое не позволяет программному процессу достичь оператора, выполняющего код для закрытия этих ресурсов.

В любом случае, открытые соединения, оставшиеся от ресурсов, потребляют память, и если с ними не разобраться, они могут ухудшить производительность и даже привести к ошибке OutOfMemoryError.

Как предотвратить это?

  • Всегда используйте блок finally для закрытия ресурсов

  • Код (даже в блоке finally), закрывающий ресурсы, сам не должен содержать исключений.

  • При использовании Java 7+ можно использовать блок try-with-resources.

3.3. Неправильная имплементация equals() и hashCode()

При определении новых классов очень распространенной ошибкой является отсутствие надлежащих переопределенных методов для equals() и hashCode().

HashSet и HashMap используют эти методы во многих операциях, и если они переопределены неправильно, то могут стать источником потенциальных проблем с утечкой памяти.

Давайте рассмотрим как пример тривиальный класс Person и используем его в качестве ключа в HashMap

public class Person {
    public String name;
    
    public Person(String name) {
        this.name = name;
    }
}

Теперь мы вставим дубликаты объектов Person в Map, использующую этот ключ.

Помните, что Map не может содержать дубликаты ключей:

@Test
public void givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() {
    Map<Person, Integer> map = new HashMap<>();
    for(int i=0; i<100; i++) {
        map.put(new Person("jon"), 1);
    }
    Assert.assertFalse(map.size() == 1);
}

Здесь мы используем Person в качестве ключа. В связи с тем, что Map не допускает дублирования ключей, то мы вставили в качестве ключа дубликаты объектов Person, что не должно увеличивать память.

Но поскольку мы не определили правильный метод equals(), дубликаты объектов накапливаются и увеличивают память, поэтому в памяти мы видим больше одного объекта. Куча в VisualVM в этом случае выглядит следующим образом:

Однако, если бы мы правильно переопределили методы equals() и hashCode(), то в этой Map существовал бы только один объект Person.

Давайте рассмотрим правильную имплементацию equals() и hashCode() для нашего класса Person:

public class Person {
    public String name;
    
    public Person(String name) {
        this.name = name;
    }
    
    @Override
    public boolean equals(Object o) {
        if (o == this) return true;
        if (!(o instanceof Person)) {
            return false;
        }
        Person person = (Person) o;
        return person.name.equals(name);
    }
    
    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + name.hashCode();
        return result;
    }
}

В этом случае будут верны следующие утверждения:

@Test
public void givenMap_whenEqualsAndHashCodeNotOverridden_thenMemoryLeak() {
    Map<Person, Integer> map = new HashMap<>();
    for(int i=0; i<2; i++) {
        map.put(new Person("jon"), 1);
    }
    Assert.assertTrue(map.size() == 1);
}

После правильного переопределения equals() и hashCode() куча для той же программы выглядит следующим образом:

Другой пример — использование инструмента ORM, такого как Hibernate, который применяет методы equals() и hashCode() для анализа объектов и сохраняет их в кэше.

Вероятность утечки памяти довольно высока, если эти методы не переопределены, поскольку Hibernate не сможет сравнивать объекты и заполнит свой кэш их копиями.

Как предотвратить это?

  • Как правило, на практике,  при определении новых сущностей всегда переопределяйте методы equals() и hashCode().

  • Недостаточно их просто переопределить, это необходимо сделать оптимальным образом. Для получения дополнительной информации ознакомьтесь с нашими учебными пособиями Generate equals() and hashCode() with Eclipse и Guide to hashCode() in Java.

3.4. Внутренние классы, которые ссылаются на внешние 

Это происходит в случае нестатических внутренних классов (анонимных классов). Для инициализации они всегда требуют экземпляр внешнего класса.

Каждый нестатический внутренний класс по умолчанию имеет неявную ссылку на содержащий его класс. Если мы используем объект этого внутреннего класса в приложении, то даже после того, как объект нашего содержащего внешнего класса покинет область видимости, он не будет убран в мусор.

Рассмотрим класс, который содержит ссылки на множество громоздких объектов и имеет нестатический внутренний класс. При создании объекта только внутреннего класса, модель памяти выглядит следующим образом:

Однако, если мы просто объявим внутренний класс как статический, то память уже будет выглядеть так:

Как предотвратить это?

  • Если внутреннему классу не нужен доступ к членам внешнего класса, подумайте о том, чтобы превратить его в статический.

3.5. Через методы finalize()

Использование финализаторов - еще один источник потенциальных проблем с утечкой памяти. Когда метод finalize() класса переопределяется, то объекты этого класса не сразу убирают в мусор. Вместо этого GC ставит их в очередь на финализацию, которая происходит позже.

Кроме того, если код метода finalize(), не является оптимальным, а также очередь финализации не успевает за сборщиком мусора Java, то рано или поздно приложение столкнется с ошибкой OutOfMemoryError.

Для демонстрации возьмем класс, в котором мы переопределили метод finalize(), и его выполнение занимает немного времени. Когда большое количество объектов данного класса собирается в мусор, то в VisualVM это выглядит так:

Однако если мы просто удалим переопределенный метод finalize(), то та же программа даст следующий ответ:

Как предотвратить это?

  • Мы всегда должны избегать финализаторов

Более подробно о finalize() читайте в разделе 3 (Как избежать использования финализаторов) нашего руководства по методу finalize в Java.

3.6. Интернированные строки

Пул строк Java претерпел значительные изменения в Java 7, когда он был перенесен из PermGen в HeapSpace. Однако для приложений, работающих на версии 6 и ниже, мы должны быть более внимательны при работе с большими строками.

Если мы считываем огромный объект-массив String и вызываем для него intern(), то он попадает в пул строк, который находится в PermGen (постоянной памяти) и будет оставаться там до тех пор, пока работает наше приложение. Это блокирует память и создает большую ее утечку в нашем приложении.

PermGen для этого случая в JVM 1.6 выглядит в VisualVM следующим образом :

В отличие от этого, если мы просто читаем строку из файла и не интернируем ее, PermGen выглядит так:

Как предотвратить это?

  • Самый простой способ решить эту проблему — обновить Java до последней версии, так как начиная с Java версии 7 пул строк перемещен в HeapSpace.

  • При работе с большими строками увеличьте размер пространства PermGen, чтобы избежать возможных ошибок OutOfMemoryErrors:

-XX:MaxPermSize=512m

3.7. Использование ThreadLocals

ThreadLocal (подробно рассматривается в учебнике "Введение в ThreadLocal в Java") - это конструкция, которая дает нам возможность изолировать состояние для конкретного потока, тем самым позволяя достичь его безопасности.

В этой конструкции каждый поток хранит неявную ссылку на копию переменной ThreadLocal и будет поддерживать только свою собственную независимую копию, вместо совместного использования ресурса несколькими потоками, в течение всего времени, пока поток активен.

Несмотря на все преимущества, переменные ThreadLocal являются спорными, поскольку они могут приводить к утечкам памяти при неправильном использовании. Joshua Bloch однажды прокомментировал применение локальных переменных потоков:

Неаккуратное использование пулов потоков в сочетании с небрежным применением локальных переменных потоков может привести к непреднамеренному удержанию объектов, как было отмечено во многих местах. Но возлагать вину на локальные переменные неоправданно.

Утечки памяти при использовании ThreadLocals

Предполагается, что ThreadLocals будут утилизироваться, как только удерживающий их поток перестанет существовать. Но проблема возникает, когда ThreadLocals применяются вместе с современными серверами приложений.

Современные серверы приложений используют пул потоков для обработки запросов вместо создания новых (например, Executor в Apache Tomcat). Более того, они также используют отдельный загрузчик классов.

Поскольку пулы потоков в серверах приложений работают на основе концепции повторного использования, они никогда не утилизируются - их используют повторно для обслуживания другого запроса.

Теперь, если какой-либо класс создает переменную ThreadLocal, но явно не удаляет ее, то копия этого объекта останется в воркере Thread даже после остановки веб-приложения, тем самым препятствуя утилизации объекта.

Как предотвратить это?

  • Хорошей практикой является очистка ThreadLocals, когда они больше не используются — ThreadLocals предоставляет метод remove(), который удаляет значение текущего потока для этой переменной.

  • Не используйте ThreadLocal.set(null) для очистки значения — он в действительности не очищает, а вместо этого ищет Map, связанную с текущим потоком, и устанавливает пару ключ-значение как текущий поток и null соответственно

  • Еще лучше рассматривать ThreadLocal как ресурс, который должен быть закрыт в блоке finally, чтобы быть уверенным в его закрытии во всех случаях, даже при исключении:

try {
    threadLocal.set(System.nanoTime());
    //... further processing
}
finally {
    threadLocal.remove();
}

4. Другие стратегии борьбы с утечками памяти

Хотя универсального решения при борьбе с утечками памяти не существует, есть некоторые способы, с помощью которых их можно минимизировать.

4.1. Включить профилирование

Профилировщики Java — это инструменты, которые отслеживают и диагностируют утечки памяти в приложении. Они анализируют, что происходит внутри нашего приложения — например, как выделяется память.

Используя профилировщики, можно сравнить различные подходы и найти области, где оптимально используются наши ресурсы.

В разделе 3 этого руководства мы использовали Java VisualVM. Пожалуйста, ознакомьтесь с нашим руководством по профилировщикам Java, чтобы узнать о различных типах профилировщиков, таких как Mission Control, JProfiler, YourKit, Java VisualVM и Netbeans Profiler.

4.2. Подробная сборка мусора

При активации подробной сборки мусора мы отслеживаем детальную трассировку GC. Чтобы включить эту функцию, нам нужно добавить следующее в конфигурацию JVM:

-verbose:gc

Добавив этот параметр, мы сможем увидеть подробности того, что происходит внутри GC:

4.3. Использование ссылочных объектов для предотвращения утечек памяти

Для борьбы с утечками памяти можно также воспользоваться ссылочными объектами в Java, которые поставляются с пакетом java.lang.ref. С помощью пакета java.lang.ref вместо прямых ссылок на объекты мы используем специальные ссылки, которые позволяют легко собирать мусор.

Очереди ссылок предназначены для того, чтобы мы знали о действиях, выполняемых сборщиком мусора. Для получения дополнительной информации прочитайте Baeldung-учебник "Мягкие ссылки в Java", а именно раздел 4.

4.4. Предупреждения об утечке памяти в Eclipse

Для проектов на JDK 1.5 и выше Eclipse выдает предупреждения и ошибки всякий раз, когда сталкивается с очевидными случаями утечки памяти. Поэтому при разработке в Eclipse мы можем регулярно посещать вкладку "Проблемы" и быть более бдительными в отношении предупреждений об утечке памяти (если таковые имеются):

4.5. Бенчмаркинг

Мы можем измерить и проанализировать производительность Java-кода, выполняя эталонные тесты. Таким образом, мы можем сравнить производительность альтернативных подходов к выполнению одной и той же задачи. Это поможет нам выбрать лучший из них и поможет сэкономить память.

Для получения более подробной информации о бенчмаркинге, ознакомьтесь с нашим учебным пособием "Микробенчмаркинг с Java".

4.6. Обзоры кода

Наконец, у нас всегда есть классический, старый добрый способ — сделать простой обзор кода.

В некоторых случаях даже этот тривиальный на первый взгляд метод может помочь в устранении некоторых распространенных проблем утечки памяти.

5. Заключение

Говоря простым языком, мы можем рассматривать утечку памяти как болезнь, которая снижает производительность нашего приложения, блокируя жизненно важные ресурсы памяти. И, как и все другие болезни, если ее не лечить, со временем она может привести к фатальным сбоям приложения.

Решить проблему утечки памяти непросто, и ее обнаружение требует высокого мастерства и владения языком Java. При борьбе с утечками памяти не существует универсального решения, поскольку они могут возникать из-за множества разнообразных событий.

Однако, если мы будем использовать лучшие практики и регулярно проводить тщательный анализ кода и профилирование, то сможем свести к минимуму риск утечки памяти в нашем приложении.

Фрагменты кода, которые использовались для генерации ответов VisualVM, показанных в этом руководстве, доступны на GitHub.


Материал подготовлен в рамках курса «Нагрузочное тестирование». Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем курса — приглашаем на день открытых дверей онлайн. Регистрация здесь.

Комментарии (20)


  1. Artyomcool
    15.11.2021 22:20
    +2

    Позволю себе немного критики предложенных советов.

    При использовании синглтонов полагайтесь на имплементацию, которая лениво, а не жадно загружает объект

    Чаще всего это просто усложняет код без практической пользы. JLS утверждает, что загрузка класса не производит видимых эффектов до первого использования класса. Если вам (зачем-то) потребовался синглтон, то следует воспользоваться именно этим свойством, а именно производить инициализацию в статическом поле (не создавая статических методов в этом классе, не наследуя его и не пропуская через Reflection API и еже с ними).

    Как правило, на практике,  при определении новых сущностей всегда переопределяйте методы equals() и hashCode().

    Напротив, лучше НЕ определять эти методы без нужды, потому что сделать это корректно зачастую очень непросто, если вообще возможно. Вместо этого лучше не использовать в качестве ключей те объекты, что не были созданны именно для этого.

    Самый простой способ решить эту проблему — обновить Java до последней версии, так как начиная с Java версии 7 пул строк перемещен в HeapSpace.

    При работе с большими строками увеличьте размер пространства PermGen, чтобы избежать возможных ошибок OutOfMemoryErrors

    Намного лучше не использовать intern вообще. Лапками и с любовью сделанный локальный кэш справится точно не хуже.

    Для борьбы с утечками памяти можно также воспользоваться ссылочными объектами в Java, которые поставляются с пакетом java.lang.ref

    С одной стороны, мы советуем не пользоваться finalize, т.к. он наносит ущерб сборке мусора нетривиальным жизненным циклом, с другой стороны забываем, что <Weak/Soft/Phantom>Reference тоже имеют схожие эффекты (разные в зависимости от настроек сборки мусора). Это, кстати, одна из причин, почему утекают DirectByteBuffer'ы.

    Хочется написать какой-то вывод, но он полностью совпадает с выводом статьи: универсального способа борьбы нет, и только опыт сможет подсказать, где и что именно может пойти не так.


    1. csl
      15.11.2021 23:02

      не пользоваться finalize

      И Cleaner.


      1. Artyomcool
        15.11.2021 23:07
        +1

        Это де-факто Phantom reference + reference queue.


    1. HDDimon
      15.11.2021 23:49

      А можно подробнее про причину утечек ByteBuffer?


      1. Artyomcool
        16.11.2021 00:36
        +1

        Чтобы замапить, например, файл в память через DirectByteBuffer, производится системный вызов mmap (если говорим, например, про Linux).

        А дальше вопрос: когда безопасно освобождать DirectByteBuffer, вызывая unmap, при условии, что работа с такими примитивами происходит средствами ОС и в целом на стороне JVM контролируется слабо (потому что performance impact вносить не хотелось бы)?

        Ответ простой: тогда, когда гарантируется, что никто больше этим объектом не пользуется. Как это гарантировать? В простейшем случае - через Cleaner, озвученный выше, который на самом деле есть набор PhantomReference и очередь на их разгребание (в Project Panama, если я ничего не путаю, используются значительно более интересные и сложные техники, но тут надо призывать уже настоящих сварщиков, чтобы за memory barrier'ы пояснили, я эту историю за обедом слышал, в код не смотрел).

        Идём дальше. Когда в очередь на разгребание попадает PhantomReference? В общем случае - это не специфицировано. Можно с натяжкой сказать, что после Full GC таки уж должно бы попасть (но в зависимости от настроек GC может попадать и раньше).

        И потом уже отдельный поток довыгребает эту чудесную очередь и позовёт unmap.

        Но вообще говоря, в проектах с нормальной нагрузкой, мы всеми силами избегаем Full GC, откладывая и откладывая освобождение ресурсов ОС, которые не безграничны и рано или поздно будут исчерпаны.

        Проблема встречается в продакшене сравнительно регулярно, не выдумана.


        1. Artyomcool
          16.11.2021 01:06

          Написал немножко чуши: именно освобождение ресурсов в Panama тоже происходит либо через Cleaner, либо можно вызвать напрямую (в DirectByteBuffer [легально] нельзя!).

          А всякие особые memory barrier'ы там вероятно (если и есть) нужны для обеспечения корректности поведения с точки зрения гарантии получения исключения, а не краха JVM при обращении к уже освобожденному ресурсу. Но это мои догадки, и относиться к этому серьезно не следует.


          1. Artyomcool
            16.11.2021 01:14
            +2

            Таки нашел:

            Shared segments rely on VM thread-local handshakes (JEP 312) to implement lock-free, safe, shared memory access; that is, when it comes to memory access, there should no difference in performance between a shared segment and a confined segment. On the other hand, MemorySegment::close might be slower on shared segments than on confined ones.

            https://github.com/openjdk/panama-foreign/blob/foreign-jextract/doc/panama_memaccess.md


        1. tsypanov
          17.11.2021 21:42

          Чтобы замапить, например, файл в память через DirectByteBuffer

          Почему бы просто не считать файл в память в виде массива байтов?


          1. Artyomcool
            17.11.2021 22:15
            +1

            Может быть множество причин. Например: файл больше, чем у вас доступно памяти. Или же вам требуется не только чтение, но и запись (в разные места файла).


  1. Cloud66
    15.11.2021 23:55
    +2

    Не нравится это определение утечки памяти. По-моему более лаконичное и четкое такое определение: утечка памяти это объекты , которые доступны, но никогда не будут использоваться.


    1. jh7
      16.11.2021 08:56
      +1

      В языках с укзателями это могут быть и недоступные объекты.


  1. irbis_al
    16.11.2021 12:07
    +1

    Вот я вставлю свои 5 копеек..Имеется кроссплатформенная ИС Desktop Swing

    Есть Jtable со своим Custom TableHtader(Многоуровневый) и Footer

    Типа как выглядит.

    https://cloud.mail.ru/public/4N3v/59puX6cFP

    https://cloud.mail.ru/public/55sJ/4RLnfQigZ

    https://cloud.mail.ru/public/Pa5S/EH8sk8ZgB

    Так вот всегда когда Footer и Header идут вместе ,то потеря 1200 байт на форме. Как только ни профилировали...смотрели граф(причем по графу видно,что обрублена форма с таблицей) И знаем какие именно объекты остаются(Это событие Listener на расширение...если мышкой расширять столбец...соответственно столбец footerа тоже должен быть раширен(и он расширяется) но GC в упор (во всех версиях java) не может разрулить,что те объекты надо очистить.


    1. tsypanov
      17.11.2021 21:46

      Возможно класс события не объявлен static, и следовательно содержит ссылку на объект родительского класса. Нестатические вложенные классы могут быть источником утечек. Как именно называется это событие расширения столбца?


      1. irbis_al
        17.11.2021 22:08

        Да нет там никаких особенностей...всё по классике

        public class TableFooter implements Border, ChangeListener, AdjustmentListener, TableColumnModelListener

        Фишка в TableColumnModelListener

        в конструкторе public TableFooter(JTable table)

        есть строчка table.getColumnModel().addColumnModelListener(this);//table.getColumnModel() Custoмный Нader многуоровневый(Пример в инете много)

        Как раз чтобы менять размер столбца

        а имплементация TableColumnModelListener тоже обычная...во всех методах repaintTable() (код ниже)

        private void repaintTable() {

        if (table.getParent() instanceof JViewport && table.getParent().getParent() instanceof JScrollPane) table.getParent().getParent().repaint();

        else table.repaint(); }

        Никаких наворотов..Но правда по графу чуть запутанно...взаимные ссылки jtable TableFooter GroupableTableHeader (extends стандартный JTableHeader) ..Но Jtable принадлжежит JInternalFrame и он обрублен...и всё что на нем убивается (JButton JPanel ) Принадлежащие JTable cellrenderи вся атрибутика ,а три осколка связаны через TableColumnModelListener остаются висеть и тратить память..что бы мы ни делали.

        При этом просто Jtable c GroupableTableHeader (без Footer) ..всё очищается


        1. tsypanov
          18.11.2021 00:14

          Так может удалить TableColumnModelListener? У него в документации сказано

          /**
           * TableColumnModelListener defines the interface for an object that listens
           * to changes in a TableColumnModel.
           */

          Получается, если схема колонок не меняется, то он вам не особо и нужен.


          1. irbis_al
            18.11.2021 08:53

            Тогда размер столбцов меняться не будет если пользователь решит раздвинуть мышкой...См.видео

            https://cloud.mail.ru/public/Ekya/qtBz8s1CZ

            А как коммерческая ИС без этого?...Это десктоп ИС ,-наибольшая проблема это касса где много чеков(около 1000 в день) там мы заложили больший задел типа -Xmx800m НА этот рост

            $JAVA_HOME/bin/java -Xmx800m -jar cis.jar

            Ну я потом в конце смены надо выйти из ИС...если будет другой оператор продолжать вторые или третьи сутки то начнуться тормоза и останов.


            1. tsypanov
              18.11.2021 11:21

              Писали ли вы в поддержку Оракла? Что там ответили?


              1. irbis_al
                18.11.2021 11:48

                Нет не писал... :-) Да и ИС с 2007 года стартовала(с дельфей ушли и с винды слава богу ,ушли как следствие) с JDK 6 тогда java неоракловая была :-)

                Потом JDK8 ещё тоже неоракловая. (Новые jdk тестируем, но не переходим)

                Я даже не представляю как этот случай описать в поддержке.Есть главная форма ...внутри неё JInternalFrame ...GUI естественно строится не прямым кодом а читает xml спецификацию(столбец базы- как называется в заголовке ,какой цвет,js скрипт (rhino) поведение столбца (Типа подсвечивать какое-то значение) )из базы , читает metadata jdbc,- связывает с CRUID диалогом(тоже xml спецификация) .

                Чтобы обрубить все эти зависимости и дать чистое решение в котором проблема ,-ещё та работа.

                Вообще в java с GUI довольно часто встречаемся с утечками..но всё время лечили(за исключением вот этого случая)

                При этом java без GUI или с почти не меняющимся GUI утечек никогда не наблюдал.


                1. tsypanov
                  22.11.2021 11:58

                  Напишите минималистичный пример, который показывает проблему, залейте на Гитхаб, указав в РИДМИ как пошагово воспроизвести и дайте ораклоидам ссылку.


  1. therealalexz
    20.11.2021 22:31

    в 2021 еще кто то пишет в еклипсе