Небезпеки методу finalize
А у нас тут можна отримати грант на тестовий період Яндекс.Хмари. Варто лише у полі «секретний пароль» запровадити «Хабр»
Читають зараз
Безкоштовна YouTube-трансляція Joker 2017: Java 9, Concurrency, GC, Spring та, звичайно, паззлери
Як ми позбулися пауз GC за допомогою власного java off-heap storage рішення
Як боротися з паузами java програми, не чіпаючи GC
Коментарі 19
Загалом, непогано, корисна пам'ятка.
Лише два коментарі:
Краще звільняти ресурси програмно, а в методі finalize логувати, якщо цього чомусь зроблено не було, щоб вчасно знайти і полагодити проблему.
Якщо використати власний логер, є ймовірність, що він уже буде фіналізований у момент фіналізації нашого класу. Наприклад, так може бути при використанні System.runFinalizersOnExit(), якщо останній рядок програми закриває логер.
7. Будь-які винятки викинуті в тілі методу будуть проігноровані.
Який висновок згодом до завершення методів сприйняття завершення цього об'єкта буде halted
Справді, специфікація не стверджує, що складання не буде, але на практиці пам'ять не звільняється і рано чи пізно на нас чекає ООМ. У мене так було в одному проекті і найбільше вразило, що виняток тишком-нишком ігнорується. Звичайно, може все і від рантайму залежить і той же OpenJDK на це не страждає.
Ось знайшовся експеримент одного ентузіаста: elliottback.com/wp/java-memory-leaks-w-finalize-examples/ Думаю, за бажання можна й інші знайти.
Якщо у вашого логера є метод finalize, то потрібно використовувати захисну техніку: у логері має бути волатайл прапор, який виставляється при його фіналізації, а всі метод логування спочатку перевіряють цейпрапор.
І про друге доповнення теж дякую.
Якщо чесно, я не думаю, що він так помітно швидше. Я думаю, що річ тут в іншому. Залишимо за кадром те, що у finalize() семантика кострубата до неможливості — будемо тільки про продуктивність: якщо ви використовуєте finalize(), то пам'ять під самим вашим об'єктом (яка в купі яви) не буде звільнена, поки фіналізація не відпрацює. Більше того — не буде звільнено пам'ять під усіма об'єктами, які можна досягти з вашого об'єкта (вони повинні залишатися досяжними, поки не відпрацює finalize). Це створює безліч неочевидних проблем: наприклад, мені складно уявити, як можна реалізувати збір-сміття-з-фіналізацією для об'єктів у молодому поколінні, які збираються копіювальним GC. Майже напевно всі об'єкти з finalize() будуть перенесені в old-gen, і будуть збиратися fullGC (начебто навіть десь явно описувалося, але точно не згадаю).
У той же час з використанням фантомних посилань у рантайму не буде жодних проблем зі звільненням пам'яті в купі яви — пам'ять під самим об'єктом, і всіма об'єктами, які можна досягти тільки з нього, можна звільнити відразу, як тільки сам об'єкт стане недосяжним. У тому числі й одразу в молодому поколінні, швидким копіювальним збирачем. Адже Runnable у Cleaner-і зберігає (==повинен зберігати - в ідеалі) посилання тільки на _зовнішні_ ресурси, на ресурси за межами купи. Вони — і тільки вони — і будуть чекати, поки дійде справа до обробки вмісту ReferenceQueue. Тобто цей механізм справді набагато дешевше за навантаженням на систему управління пам'яттю в самій яві.
Ну, це мої спекуляції. Сам об'єкт буде звільнений — а Runnable, який ми створили, щоб звільнити зовнішні ресурси — ні. Чекати-таки поки його з черги не витягнуть. Тож тут непростий балансвиходить, між розміром цього Runnable, і розміром вихідного об'єкта+ всі доступні тільки з нього.
У будь-якому випадку, у finalize() така хитромучена семантика, що її реалізація напевно дорогого коштує саме через вимученість. Прозоріша логіка роботи ReferenceQueue швидше за все і реалізується простіше та ефективніше