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

Я медленно работаю через Bruce Eckel. Думаю, в четвертом выпуске Java, и следующая проблема меня озадачила:

Создайте класс с методом finalize(), который печатает сообщение. В main() создайте объект своего класса. Измените предыдущее упражнение так, чтобы ваш finalize() всегда вызывался.

Это то, что я закодировал:

public class Horse {
    boolean inStable;
    Horse(boolean in){
        inStable = in;
    }   
    public void finalize(){
        if (!inStable) System.out.print("Error: A horse is out of its stable!");
    }
}
public class MainWindow {
    public static void main(String[] args) {
        Horse h = new Horse(false);
        h = new Horse(true);
        System.gc();
    }
}

Он создает новый объект Horse с булевым inStable, установленным в false. Теперь, в методе finalize(), он проверяет, есть ли inStable false. Если это так, он печатает сообщение.

К сожалению, ни одно сообщение не печатается. Поскольку условие оценивается как true, я предполагаю, что finalize() не вызывается в первую очередь. Я запускал программу несколько раз и видел сообщение об ошибке только пару раз. У меня создалось впечатление, что при вызове System.gc() сборщик мусора будет собирать любые объекты, на которые не ссылаются.

Google корректный ответ дал мне эту ссылку, в которой содержится гораздо более подробный, сложный код. Он использует методы, которые я раньше не видел, например System.runFinalization(), Runtime.getRuntime() и System.runFinalizersOnExit().

Может ли кто-нибудь дать мне лучшее представление о том, как работает finalize() и как заставить его работать, или пройти через то, что делается в коде решения?

Ответ 1

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

Вы можете сказать системе, чтобы попытаться запустить ожидающие финализаторы сразу, вызвав метод System.runFinalization после сбора мусора.

Но если вы хотите принудительно запустить финализатор, вы должны называть его самостоятельно. Сборщик мусора не гарантирует, что какие-либо объекты будут собраны или что будут вызываться финализаторы. Это только делает "лучшее усилие". Однако редко бывает, что вам нужно будет заставить финализатор работать в реальном коде.

Ответ 2

Вне сценариев игрушек вообще невозможно гарантировать, что a finalize всегда будет вызываться на объектах, к которым не существует "значимых" ссылок, потому что сборщик мусора не знает, какие ссылки "значимы". Например, объект ArrayList -like может иметь "ясный" метод, который устанавливает его счетчик в ноль и делает все элементы в массиве поддержки пригодными для перезаписывания будущими вызовами Add, но на самом деле не очищает элементов в этом массиве поддержки. Если у объекта есть массив размером 50, а его Count равно 23, тогда не может быть пути выполнения, посредством которого код может когда-либо проверять ссылки, хранящиеся в последних 27 слотах массива, но не было бы способа для сборщик мусора, чтобы это знать. Следовательно, сборщик мусора никогда не будет называть finalize для объектов в этих слотах, если до или после того, как контейнер переполнит эти слоты массива, контейнер покинул массив (возможно, в пользу меньшего) или все корневые ссылки на сам контейнер были уничтожены или иным образом перестали существовать.

Существуют различные способы побудить систему вызывать finalize на любых объектах, для которых не существует прочных корневых ссылок (что, кажется, является точкой вопроса и какие другие ответы уже рассмотрены), но я думаю, важно отметить различие между множеством объектов, к которым существуют сильные корневые ссылки, и набором объектов, которые могут интересоваться кодом. Эти два набора в основном перекрываются, но каждый набор может содержать объекты, не относящиеся к другому. Финализаторы объектов выполняются, когда GC определяет, что объекты больше не будут существовать, но для существования финализаторов; которые могут или не совпадают с временным кодом, которым они перестают быть интересными для любого. Хотя было бы полезно, если бы вы могли заставить финализаторы работать на всех объектах, которые перестали представлять интерес, что вообще невозможно.

Ответ 3

Вызов метода сборщика габаритов (System.gc()) предлагает, что виртуальная машина Java тратит усилия на переработку неиспользуемых объектов, чтобы сделать память, которую они в настоящее время занимают, для быстрого повторного использования (то есть ее просто предложение jvm и не связывает его для выполнения действия тогда и там, оно может или не может сделать то же самое). Когда управление возвращается из вызова метода, виртуальная машина Java приложила все усилия, чтобы освободить место от всех отброшенных объектов. finalize() вызывается сборщиком мусора на объекте, когда сбор мусора определяет, что больше нет ссылок на объект

Ответ 4

Вот что сработало для меня (частично, но это иллюстрирует идею):

class OLoad {

    public void finalize() {
        System.out.println("I'm melting!");
    }
}

public class TempClass {

    public static void main(String[] args) {
        new OLoad();
        System.gc();
    }
}

Строка new OLoad(); делает трюк, поскольку он создает объект без ссылки. Это помогает System.gc() запускать метод finalize(), поскольку он обнаруживает объект без ссылки. Высказывание чего-то типа OLoad o1 = new OLoad(); не будет работать, так как оно создаст ссылку, которая живет до конца main(). К сожалению, это работает большую часть времени. Как указывали другие, нет способа гарантировать, что finalize() будет всегда вызываться, кроме как называть его самостоятельно.

Ответ 5

Запустите новый конструктор() и System.gc() более двух раз.

public class Horse {
    boolean inStable;
    Horse(boolean in){
        inStable = in;
    }   
    public void finalize(){
        if (!inStable) System.out.print("Error: A horse is out of its stable!");
    }
}
public class MainWindow {
    public static void main(String[] args) {
        for (int i=0;i<100;i++){
            Horse h = new Horse(false);
            h = new Horse(true);
            System.gc();
        }
    }
}