Pull to refresh

Comments 21

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

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

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

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

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

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

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

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

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

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

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

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

И Cleaner.

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

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

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

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

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

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

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

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

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

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

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

Таки нашел:

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

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

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

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

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

UFO just landed and posted this here

Вот я вставлю свои 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) не может разрулить,что те объекты надо очистить.

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

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

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) ..всё очищается

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Спасибо, за статью! Вы писали, о том что String.intern() хранится в PermGen до конца жизни приложения. На сколько мне известно они там хранятся пока хотя бы один объект ссылается на них.

Sign up to leave a comment.