Чому ви коли-небудь реалізуєте finalize ()?


371

Я читав багато запитань про новинку Java, finalize()і вважаю це дивним, що ніхто не зрозумів, що finalize () є ненадійним способом очищення ресурсів. Я бачив, як хтось коментує, що вони використовують це для очищення з'єднань, що насправді страшно, оскільки єдиний спосіб наблизитися до гарантії закриття з’єднання - це здійснити спробу (catch) нарешті.

Я не навчався в CS, але я вже майже десятиліття професійно програмую на Java, і ніколи не бачив, щоб хтось реалізував finalize() у виробничу систему. Це все ще не означає, що він не має свого використання, або що люди, з якими я працював, робили це правильно.

Отже, моє запитання полягає в тому, які випадки використання є для реалізації finalize() які не можна обробляти надійніше за допомогою іншого процесу чи синтаксису в мові?

Будь ласка, надайте конкретні сценарії чи свій досвід, просто повторивши текстову книгу Java, або остаточне використання не допрацьоване, як і не в цьому питанні.


Метод HI finalize () дуже добре пояснив тут howtodoinjava.com/2012/10/31/…
Sameer Kazi

2
Більшість програм і код бібліотеки ніколи не використовуються finalize(). Однак код бібліотеки платформи , такий як SocketInputStream, який управляє власними ресурсами від імені абонента, робить це, щоб спробувати мінімізувати ризик витоку ресурсів (або використовує еквівалентні механізми, такі як PhantomReference, які були додані пізніше.) Тому екосистема потребує їх , хоча 99,9999% розробників ніколи не напишуть його.
Брайан Гец

Відповіді:


231

Ви можете використовувати його як зворотний стоп для об'єкта, що містить зовнішній ресурс (сокет, файл тощо). Реалізуйте close()метод та документ, який його потрібно викликати.

Реалізація finalize()робитиclose() обробку, якщо ви виявите, що вона не була зроблена. Можливо, з чимось скинутимstderr вказати, що ви прибираєте після помилкового абонента.

Це забезпечує додаткову безпеку у виняткових ситуаціях. Не кожен абонент збирається зробити правильноtry {} finally {} речі кожен раз. Невдало, але правда в більшості середовищ.

Я згоден, що це рідко потрібно. І як зазначають коментатори, це стосується накладних витрат на GC. Використовуйте лише те, якщо вам потрібна безпека "ременів та підтяжок" у тривалому додатку.

Я бачу, що, як у Java 9, Object.finalize()застаріла! Вони вказують на нас java.lang.ref.Cleanerі java.lang.ref.PhantomReferenceяк на альтернативи.


44
Просто переконайтесь, що виняток ніколи не викидається шляхом finalize (), або збирач сміття не буде продовжувати очищення цього об’єкта, і ви отримаєте витік пам'яті.
скафман

17
Фіналізація не гарантовано називається, тому не розраховуйте на звільнення ресурсу.
Flicken

19
skaffman - я не вірю в це (окрім деякої баггічної реалізації JVM). Від Object.finalize () javadoc: Якщо невстановлене виключення передається методом finalize, виняток ігнорується і завершення цього об'єкта припиняється.
Джон М

4
Ваша пропозиція може довго ховати помилок людей (не дзвонячи близько). Я б не рекомендував цього робити.
kohlerm

64
Я фактично використовував фіналізацію саме з цієї причини. Я успадкував код, і він був дуже баггі і мав тенденцію залишати підключення до бази даних відкритими. Ми змінили з'єднання, щоб містити дані про те, коли і де вони були створені, а потім реалізували завершення, щоб записати цю інформацію, якщо з'єднання було не закрито належним чином. Це виявилося чудовою допомогою для відстеження незамкнутих з'єднань.
brainimus

174

finalize()є натяком на JVM, що може бути непогано виконати свій код у невизначений час. Це добре, коли ви хочете, щоб код таємничим чином не запускався.

Робити що-небудь суттєве у фіналізаторах (в основному що завгодно, крім журналу), також добре в трьох ситуаціях:

  • ви хочете отримати азартні ігри, що інші доопрацьовані об’єкти все ще будуть у стані, який решта вашої програми вважає дійсним.
  • ви хочете додати багато перевіряючого коду до всіх методів усіх класів, які мають фіналізатор, щоб переконатися, що вони поводяться правильно після завершення.
  • Ви хочете випадково воскресити доопрацьовані об’єкти і витратити багато часу на намагання з'ясувати, чому вони не працюють, та / або чому вони не завершуються, коли їх згодом звільняють.

Якщо ви думаєте, що вам потрібно допрацювати (), іноді те, що ви дійсно хочете, - це посилання на фантом (яке у наведеному прикладі може містити чітке посилання на з'єднання, яке використовує його реферан, і закривати його після того, як посилання на фантом посилається на чергу). Це також властивість, що вона може таємниче ніколи не запускатися, але принаймні не може викликати методи на або воскрешати остаточні об'єкти. Отже, це просто підходить для ситуацій, коли вам зовсім не потрібно чисто закривати цей зв’язок, але ви хотіли б, а клієнти вашого класу не можуть або не зателефонують закрити себе (що насправді досить справедливо - що' s сенс взагалі мати сміттєзбірник, якщо ви розробляєте інтерфейси, які потребують певних дій перед збиранням? Це просто повертає нас у часи малока / вільного.)

Інший раз вам потрібен ресурс, який, на вашу думку, ви управляєте, щоб бути більш надійним. Наприклад, чому потрібно закрити це з'єднання? В кінцевому рахунку він повинен базуватися на якомусь вході / виводу, наданому системою (сокет, файл, будь-який інший), то чому б ви не могли покластися на систему, щоб закрити її для вас, коли досягається найнижчий рівень ресурсу? Якщо сервер на іншому кінці абсолютно вимагає, щоб ви чисто закрили з'єднання, а не просто відключили розетку, то що станеться, коли хтось перейде через силовий кабель машини, на якому працює ваш код, або мережа, що втручається?

Відмова: Я раніше працював над реалізацією JVM. Я ненавиджу фіналізаторів.


76
Захист до правосуддя крайнього сарказму.
Сприйняття

32
Це не відповідає на запитання; він просто говорить про всі недоліки, finalize()не наводячи жодних причин, хтось дійсно захотів би їх використати.
Механічний равлик

1
@supercat: посилання на фантоми (з цього приводу всі посилання) були введені на Java 2
Стів Джессоп

1
@supercat: вибачте, так, під "посиланнями" я мав на увазі java.lang.ref.Referenceта його підкласи. У Java 1 були змінні :-) І так, він також мав фіналізатори.
Стів Джессоп

2
@SteveJessop: Якщо конструктор базового класу попросить інші об'єкти змінити свій стан від імені об'єкта, що будується (дуже поширений зразок), але ініціалізатор поля похідного класу видає виняток, частково побудований об'єкт повинен негайно очистити після себе (тобто повідомляти тих сторонніх організацій, що їхні послуги більше не потрібні), але ні Java, ні .NET не пропонують жодної рівномірно чистої схеми, через яку це може статися. Дійсно, вони, здається, виходять зі свого шляху, щоб ускладнити посилання на те, що потребує очищення, щоб дійти до коду очищення.
supercat

58

Просте правило: ніколи не використовуйте фіналізатори.

Сам факт, що об’єкт має фіналізатор (незалежно від того, який код він виконує), достатньо, щоб спричинити значні накладні витрати на вивезення сміття.

Зі статті Брайана Геца:

Об'єкти з фіналізаторами (ті, у яких є нетривіальний метод finalize ()) мають значні накладні витрати в порівнянні з об'єктами без фіналізаторів, і їх слід використовувати економно. Закінчувані об'єкти розподіляють як повільніше, так і повільніше збирати. Під час розподілу JVM повинен реєструвати будь-які доопрацьовані об’єкти у сміттєзбірнику, і (принаймні, у впровадженні HotSpot JVM) доопрацьовані об’єкти повинні слідувати повільнішому шляху розподілу, ніж більшість інших об'єктів. Так само повільніше збирати доопрацьовані об'єкти. Потрібно щонайменше два цикли вивезення сміття (в кращому випадку), перш ніж фіналізований об’єкт може бути відкликаний, і сміттєзбірник повинен зробити додаткову роботу, щоб викликати фіналізатор. Результат - це більше часу, витраченого на розподіл та збирання предметів, і більший тиск на сміттєзбірник, оскільки пам'ять, яка використовується недосяжними фіналізованими об'єктами, зберігається довше. Поєднайте це з тим, що фіналізатори не гарантовано працюють у будь-які передбачувані часові рамки або навіть взагалі, і ви можете бачити, що існує порівняно мало ситуацій, для яких доопрацювання є правильним інструментом для використання.


46

Єдиний раз, коли я використовував доопрацювання у виробничому коді, це здійснити перевірку, чи очищені ресурси певного об'єкта, а якщо ні, то записувати дуже голосове повідомлення. Він насправді не намагався зробити це сам, він просто кричав, якщо це не було зроблено належним чином. Виявилося досить корисним.


35

Я займаюся Java професійно з 1998 року, і ніколи не реалізовував finalize(). Не раз.


Як тоді знищити об'єкт громадського класу? Це викликає проблеми в АПД. В основному, це припустимо перезавантажити сторінку (отримання свіжих даних з API), але це забирає старий об’єкт і показує результати
InfantPro'Aravind '

2
@ InfantPro'Aravind 'завершення виклику не видаляє об'єкт. Це метод, який ви реалізуєте, що JVM може вибрати для виклику, якщо і коли сміття збирає ваш об’єкт.
Пол Томблін

@PaulTomblin, о, дякую за цю деталь. Будь-яка пропозиція оновити сторінку на ADF?
InfantPro'Aravind '

@ InfantPro'Aravind 'Ніякої ідеї, ніколи не використовувався АПД.
Пол Томблін

28

Прийнята відповідь хороша, я просто хотів додати, що зараз є спосіб функціональності доопрацювати, фактично не використовуючи її взагалі.

Подивіться на класи "Довідник". Слабка довідка, Phantom Reference & Soft Reference.

Ви можете використовувати їх для збереження посилання на всі ваші об'єкти, але ця посилання АЛЕ не зупинить GC. Акуратне питання полягає в тому, що ви можете змусити його викликати метод, коли він буде видалений, і цей метод можна гарантовано викликати.

Щодо доопрацювання: я використав финализирование один раз, щоб зрозуміти, які об’єкти звільняються. Ви можете грати в кілька акуратних ігор зі статикою, підрахунком посилань та ін. - але це було лише для аналізу, але слідкуйте за таким кодом (не просто на завершення, але саме там ви, швидше за все, це побачите):

public void finalize() {
  ref1 = null;
  ref2 = null;
  othercrap = null;
}

Це знак того, що хтось не знав, що вони роблять. "Прибирання", як це, практично ніколи не потрібно. Коли клас GC'd, це робиться автоматично.

Якщо ви знайдете такий код під час фіналізації, гарантується, що людина, яка його написала, розгубилася.

Якщо в іншому місці, може бути, що код є дійсним виправленням на погану модель (клас залишається довгий час, і чомусь речі, на які він посилався, повинні бути звільнені вручну, перш ніж об'єкт буде GC'd). Як правило, це тому, що хтось забув видалити слухача чи щось таке, і не може зрозуміти, чому їх об'єктом не є GC'd, тому вони просто видаляють речі, на які він посилається, знизають плечима та йдуть геть.

Його ніколи не слід використовувати для прибирання речей "Швидше".


3
Фентомні посилання - це кращий спосіб відстежувати, які об’єкти звільняються, я думаю.
Білл Мішель

2
наголосити на наданні найкращого пояснення "слабкої орієнтації", яку я коли-небудь чув.
sscarduzio

Мені шкода, що мені знадобилося стільки років, щоб прочитати це, але це справді приємне доповнення до відповідей.
Спенсер Кормос

27

Я не впевнений, що ви можете з цього зробити, але ...

itsadok@laptop ~/jdk1.6.0_02/src/
$ find . -name "*.java" | xargs grep "void finalize()" | wc -l
41

Тому я здогадуюсь, що Сонце знайшло деякі випадки, коли (на їхню думку) його слід використовувати.


12
Я думаю, це також питання "Чи завжди розробник Sun буде мати рацію"?
CodeReaper

13
Але скільки з цих застосувань просто реалізують або тестують підтримку, finalizeа не фактично її використовують?
Механічний равлик

Я використовую Windows. Як би ви зробили те саме в Windows?
gparyani

2
@damryfbfnetsi: Спробуйте встановити ack ( yondgrep.com ) за допомогою chocolatey.org/packages/ack . Або скористайтеся простою функцією Find in Files у $FAVORITE_IDE.
Брайан Клайн

21
class MyObject {
    Test main;

    public MyObject(Test t) {    
        main = t; 
    }

    protected void finalize() {
        main.ref = this; // let instance become reachable again
        System.out.println("This is finalize"); //test finalize run only once
    }
}

class Test {
    MyObject ref;

    public static void main(String[] args) {
        Test test = new Test();
        test.ref = new MyObject(test);
        test.ref = null; //MyObject become unreachable,finalize will be invoked
        System.gc(); 
        if (test.ref != null) System.out.println("MyObject still alive!");  
    }
}

======================================

результат:

This is finalize

MyObject still alive!

=======================================

Таким чином, ви можете зробити недосяжний екземпляр, доступний методом завершення.


21
Чомусь цей код змушує мене думати про фільми про зомбі. Ви GC ваш об'єкт, а потім він знову встає ...
steveha

вище хороші коди. Мені було цікаво, як зробити так, щоб об’єкт знову був доступний лише зараз.
lwpro2

15
ні, вище - погані коди. це приклад того, чому доопрацювати погано.
Тоні

Пам’ятайте, що виклик System.gc();не є гарантією того, що сміттєзбірник дійсно працює. Це повністю розсуд GC для очищення купи (можливо, все ще достатньо вільної купи навколо, можливо, не фрагментовано до багатьох, ...). Тож це лише скромний запит програміста "будь ласка, ти міг мене почути і бігти?" а не команда "Не біжи зараз, як я кажу!". Вибачте за те, що використовуєте мовні слова, але він говорить саме про те, як працює GC JVM.
Роланд

10

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

Я програмую на Яві з 1.0 альфа 3 (1995), і мені ще не вдається переопрацювати будь-яку проблему ...


6

Ви не повинні залежати від finalize (), щоб очистити свої ресурси для вас. finalize () не буде працювати, поки клас не буде зібраний сміття, якщо тоді. Набагато краще явно звільнити ресурси, коли ви закінчите їх використання.


5

Щоб виділити крапку у наведених вище відповідях: фіналізатори будуть виконані на самотній GC-потоці. Я чув про головну демонстрацію Sun, де розробники додали невеликий сон до деяких фіналізаторів і навмисно підняли на колінах фантастичну 3D демонстрацію.

Краще уникати, за винятком тестової діагностики.

Мислення Еккеля на Яві має хороший розділ про це.


4

Хммм, я колись використовував це для очищення об'єктів, які не поверталися до наявного пулу.

Їх пропускали багато, тому неможливо було сказати, коли їх можна сміливо повернути в басейн. Проблема полягала в тому, що вона впровадила величезну пеню під час вивезення сміття, яка була набагато більшою, ніж будь-яка економія від об'єднання об’єктів. Це було у виробництві близько місяця, перш ніж я вирвав увесь басейн, зробив все динамічним і з цим зробив.


4

Будьте уважні до того, чим займаєтесь finalize(). Особливо, якщо ви використовуєте його для таких речей, як дзвінки close (), щоб забезпечити очищення ресурсів. Ми зіткнулися з декількома ситуаціями, коли у нас були бібліотеки JNI, пов'язані з діючим кодом Java, і за будь-яких обставин, коли ми використовували finalize () для виклику методів JNI, ми отримали дуже погану корупцію Java. Корупція не була спричинена самим кодом JNI, усі сліди пам’яті були чудовими у рідних бібліотеках. Це був той факт, що ми взагалі викликали методи JNI з фіналізації ().

Це було з JDK 1.5, який все ще широко використовується.

Ми не дізнаємось, що щось пішло не так, як набагато пізніше, але врешті-решт винуватцем завжди був метод finalize (), використовуючи виклики JNI.


3

Під час написання коду, який буде використовуватися іншими розробниками, для визволення ресурсів необхідний якийсь метод очищення. Іноді ті інші розробники забувають зателефонувати на ваш метод очищення (або закрити, або знищити, або будь-який інший). Щоб уникнути можливих витоків ресурсів, ви можете перевірити метод остаточної перевірки, щоб переконатися, що метод був викликаний, а якщо він не був, ви можете викликати його самостійно.

Багато драйверів баз даних роблять це у своїх заявах та реалізаціях підключення, щоб забезпечити невелику безпеку для розробників, які забувають зателефонувати на них.


2

Редагувати: Гаразд, це дійсно не працює. Я реалізував це і подумав, що якщо це не вдається іноді, це нормально для мене, але він навіть не закликав метод завершення одноразово.

Я не є професійним програмістом, але в своїй програмі я маю випадок, який, на мою думку, є прикладом хорошого випадку використання finalize (), тобто кеша, який записує його вміст на диск до його знищення. Оскільки це не обов'язково, що воно виконується кожного разу при знищенні, це тільки прискорює мою програму, я сподіваюся, що це я не зробив неправильно.

@Override
public void finalize()
{
    try {saveCache();} catch (Exception e)  {e.printStackTrace();}
}

public void saveCache() throws FileNotFoundException, IOException
{
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("temp/cache.tmp"));
    out.writeObject(cache);
}

Я думаю, що кеш (такого типу, який ви б передали на диск) - хороший приклад. І навіть якщо остаточне завершення не викликається, жодного поту.
foo

2

Це може бути зручно для видалення речей, які були додані до глобального / статичного місця (з потреби) та які потрібно буде видалити, коли об’єкт видалено. Наприклад:

    private void addGlobalClickListener () {
        slabAwtEventListener = новий WeakAWTEventListener (це);

        Toolkit.getDefaultToolkit (). AddAWTEventListener (слабкийAwtEventListener, AWTEvent.MOUSE_EVENT_MASK);
    }

    @Override
    захищена недійсна фіналізація () кидає Throwable {
        супер.фіналізувати ();

        if (слабкийAwtEventListener! = null) {
            Toolkit.getDefaultToolkit (). RemoveAWTEventListener (слабкийAwtEventListener);
        }
    }

Для цього потрібні додаткові зусилля, оскільки, коли слухач має чітке посилання на thisекземпляр, переданий йому в конструкторі, цей екземпляр ніколи не стане недосяжним, отже, finalize()він ніколи не очиститься. Якщо, з іншого боку, слухач має слабке посилання на цей об’єкт, як випливає з назви, ця конструкція ризикує очиститися в рази, коли цього не повинно. Життєві цикли об'єктів не є правильним інструментом для реалізації семантики додатків.
Холгер

0

iirc - ви можете використовувати метод доопрацювання як засіб впровадження механізму об'єднання для дорогих ресурсів - щоб вони також не отримували GC.


0

Як бічна примітка:

Об'єкт, який переосмислює завершення (), обробляється спеціально сміттєзбірником. Зазвичай об'єкт негайно знищується протягом циклу колекції після того, як об’єкт більше не перебуває в області застосування. Однак об'єкти, що доопрацьовуються, замість цього переміщуються до черги, де окремі потоки завершення зливають чергу та запускають метод finalize () для кожного об’єкта. Після того, як метод finalize () припиняється, об'єкт нарешті буде готовий до вивезення сміття в наступному циклі.

Джерело: остаточне завершення () застаріло на java-9


0

Ресурси (файл, сокет, потік тощо) потрібно закрити, як тільки ми з ними закінчимося. Вони, як правило, мають close()метод, який ми зазвичай називаємо в finallyрозділі try-catchтверджень. Іноді finalize()їх також можуть використовувати декілька розробників, але IMO, що не є підходящим способом, оскільки немає гарантії, що завершення буде викликано завжди.

У Java 7 у нас є оператор спробу використання ресурсів, який можна використовувати так:

try (BufferedReader br = new BufferedReader(new FileReader(path))) {
  // Processing and other logic here.
} catch (Exception e) {
  // log exception
} finally {
  // Just in case we need to do some stuff here.
}

У наведеному вище прикладі спробу-з-ресурсом автоматично закриється ресурс BufferedReaderшляхом виклику close()методу. Якщо ми хочемо, ми можемо також реалізувати Closeable у власних класах та використовувати його аналогічно. ІМО здається більш акуратним і простим для розуміння.


0

Особисто я майже ніколи не використовував, finalize()за винятком однієї рідкісної обставини: я створив власну колекцію загального типу, і я написав власний finalize()метод, який виконує такі дії:

public void finalize() throws Throwable {
    super.finalize();
    if (destructiveFinalize) {
        T item;
        for (int i = 0, l = length(); i < l; i++) {
            item = get(i);
            if (item == null) {
                continue;
            }
            if (item instanceof Window) {
                ((Window) get(i)).dispose();
            }
            if (item instanceof CompleteObject) {
                ((CompleteObject) get(i)).finalize();
            }
            set(i, null);
        }
    }
}

( CompleteObjectЦе інтерфейс я зробив , що дозволяє визначити , що ви реалізували рідко реалізуються Objectметоди , як #finalize(), #hashCode()і #clone())

Отже, використовуючи сестринський #setDestructivelyFinalizes(boolean)метод, програма, що використовує мою колекцію, може (допомогти) гарантувати, що знищення посилань на цю колекцію також знищує посилання на її вміст та розпоряджається будь-якими вікнами, які можуть ненавмисно підтримувати JVM. Я також вважав, що зупиняю будь-які нитки, але це відкрило цілу нову банку глистів.


4
Виклик finalize()на ваших CompleteObjectвипадках , як ви робите в коді вище означає , що вона може бути викликана двічі, як JVM все ще може викликати finalize()тільки ці об'єкти фактично стали недосяжними, що, в зв'язку з логікою завершення, може бути , перш ніж в finalize()методі вашої спеціальної колекції дзвонить або навіть одночасно одночасно. Оскільки я не бачу жодних заходів для забезпечення безпеки потоку у вашому методі, ви, здається, цього не знаєте…
Holger

0

У списках прийнятих відповідей про те, що закриття ресурсу під час фіналізації можна зробити.

Однак ця відповідь показує, що принаймні в java8 із компілятором JIT ви стикаєтеся з несподіваними проблемами, де іноді викликається фіналізатор ще до того, як ви закінчите читання з потоку, що підтримується вашим об'єктом.

Тож навіть у цій ситуації закликати доопрацювати не рекомендується.


Було б справно, якщо операція буде забезпечена безпекою потоку, що є обов'язковим, оскільки фіналізатори можуть називатися довільними потоками. Оскільки правильно реалізована безпека потоку передбачає, що не тільки закриття операцій, але й операції з ресурсом повинні синхронізуватися на одному об’єкті або блокуванні, між використанням і закритою операцією буде відбуватися взаємозв'язок, що також запобігає занадто рано доопрацювання. Але, звичайно, за високу ціну за все використання…
Holger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.