Почему не вызывается finalize ()?

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

public class Test
{

    public static void main(String[] args)
    {   
        Tank tank=new Tank();
        tank.fill();
        System.gc();
    } 
}  
public class Tank    {

    private boolean emptied=true;

    public void fill()
    {
        this.emptied=false;
    }

    public void empty()
    {
        this.emptied=true;
    }

    public Tank()
    {
        this.emptied=true;
    }

    protected void finalize()
    {
        if(this.emptied==false)
        {
            System.out.println("Termination Verification Error: Tank should be emptied");
        }
    }
}

person Catheryan    schedule 08.11.2013    source источник
comment
потому что у вас все еще есть ссылка на объект, так почему вы ожидаете, что он будет gc'd?   -  person tckmn    schedule 09.11.2013
comment
Значит, если они установят tank=null, это должно сработать? Честно говоря, я никогда не использовал деконструкцию в Java.   -  person Jason Sperske    schedule 09.11.2013
comment
Как правило, JVM не гарантирует запуск финализатора сразу после уничтожения объекта. Поэтому, если у вас есть код завершения, который необходимо выполнить, используйте собственный метод завершения e. г. путем реализации интерфейса Closeable / AutoCloseable. См. Эффективная Java, пункт 7: Избегайте финализаторов.   -  person Robin Krahl    schedule 09.11.2013
comment
@JasonSperske Теоретически. Установка tank=null (или установка его на что-то другое; суть в том, чтобы не было оставшихся ссылок на интересующий объект) перед вызовом System.gc(), подтолкнет это к работе. Вам все еще не гарантировано, что сборщик мусора действительно будет работать, когда вы вызываете gc(), или что он фактически отбросит объект, или что он вызовет finalize() для этого объекта - однако, если вы этого не сделаете установите tank=null, вы точно гарантируете, что ничего из этого не произойдет.   -  person Jason C    schedule 09.11.2013
comment
@ Robin Krahl --- Это очень ясно и прямо, спасибо, я воспользуюсь этим в будущем.   -  person Catheryan    schedule 09.11.2013


Ответы (2)


Метод finalize() вызывается только тогда, когда объект уничтожается и завершается сборщиком мусора, что происходит в некоторый неопределенный момент времени после отбрасывания последней ссылки на объект. Если объект никогда не освобождается (например, у вас все еще есть ссылка на него), finalize() никогда не вызывается.

Также имейте в виду, что у вас нет контроля над когда finalize() вызывается и будет ли он вообще когда-либо вызван. См. общее описание _4 _ .

Даже System.gc() не гарантирует, что сборщик мусора что-либо сделает. Это всего лишь предложение для JVM. См. общее описание _6 _

В общем, если вы пытаетесь манипулировать сборщиком мусора таким образом, чтобы выполнить эти типы проверок, обычно есть более эффективные способы. Это неправильное использование finalize() и GC.

Например, если вы пытаетесь проверить, истинно ли предполагаемое условие, рассмотрите возможность использования assert в соответствующих местах (но имейте в виду: assert не является заменой функционального if; это просто для самодокументирования и проверки условий, принятых в время проектирования).

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

public static void main(String[] args)
{   
    Tank tank=new Tank();
    tank.fill();
    // check explicitly before terminating
    if (!tank.empty()) 
        System.err.println("Warning: Ending with non-empty tank!");
} 

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

Обновление: еще один хороший вариант, который MadProgrammer любезно упомянул в комментариях ниже, - это использовать shutdown hook, если вам это подходит. Это обеспечит чистый способ выхода из кода при завершении работы, если у вашего приложения есть несколько точек выхода, которые вы не можете устранить или контролировать иначе.

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

person Jason C    schedule 08.11.2013
comment
Спасибо, Джейсон. Я понял, так что даже явно напишите finalize (), gc не будет вызывать его каждый раз. Я обнаружил, что есть решение вызывать finalize каждый раз, System.runFinalizersOnExit (true); перед gc (), но это не рекомендуется. И вот еще один вопрос, для проверки завершения, которую мы должны сделать, есть ли лучший способ сделать это? - person Catheryan; 09.11.2013
comment
@ user2970539 Есть способ получше, который я описал выше: явно проверять условие при выходе. Вы даже можете определить метод, например Tank.checkExitConditions(), который вы явно вызываете перед выходом из приложения. Если ваше приложение имеет несколько точек выхода (например, System.exit() в другом месте кода), вам также придется выполнять проверки выхода и там, хотя в этом случае вы можете подумать о том, чтобы немного очистить дизайн и попытаться выйти, только вернув с main. - person Jason C; 09.11.2013
comment
@ user2970539 Будет ли достаточно shutdownHook в этом случае? Взгляните на Runtime#addShutdownHook - person MadProgrammer; 09.11.2013
comment
@MadProgrammer Это отличное предложение, я добавил его как возможность выше. - person Jason C; 09.11.2013
comment
Спасибо, Джейсон, это очень ясно. - person Catheryan; 09.11.2013

Согласно JLS 12.6. Доработка экземпляров классов

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

person kosa    schedule 08.11.2013