Как объект помечается как завершенный в Java (чтобы метод finalize не вызывался во второй раз)?

Основной вопрос в теме, но позвольте мне показать свое видение процесса финализации в Java, чтобы я мог спросить вас немного больше.

Что ж, сборщик мусора запускает сборку мусора, помечая все живые объекты. Когда все достижимые объекты отмечены как «живые». Все остальные объекты недоступны. Следующим шагом будет проверка каждого недостижимого объекта и определение, можно ли его зачистить прямо сейчас или его нужно сначала доработать. GC думает следующим образом: если у метода finalize объекта есть тело, то этот объект подлежит финализации и должен быть финализирован; если метод finalize объекта имеет пустое тело (protected void finalize(){ }), то он не является финализируемым и может быть очищен сборщиком мусора прямо сейчас. (Прав ли я в этом?)
Все финализируемые объекты будут помещены в одну очередь для финализации позже один за другим. Насколько я понимаю, финализируемый объект может потратить много времени на помещение в очередь, ожидая своей очереди на финализацию. Это может произойти из-за того, что обычно только один поток с именем Finalizer берет объекты из очереди и вызывает их метод finalize, а когда у нас есть некоторые трудоемкие операции в методе finalize какого-либо объекта, другие объекты в очереди будут ждать завершения довольно долго. Ну, когда объект был завершен, он помечается как FINALIZED и удаляется из очереди. Во время следующего процесса сборки мусора сборщик увидит, что этот объект недоступен (снова) и имеет непустой метод финализации (снова), поэтому этот объект должен быть помещен в очередь (снова), но он выиграл а не потому, что сборщик каким-то образом видит, что этот объект был помечен как ЗАВЕРШЕННЫЙ. (Это мой главный вопрос: каким образом этот объект был отмечен как ЗАВЕРШЕННЫЙ, откуда сборщик знает, что этот объект не должен снова доработать?)


person Anton Kasianchuk    schedule 16.02.2014    source источник


Ответы (3)


Пока мы говорим о HotSpot JVM...

Сам объект НЕ ПОМЕЧЕН как завершенный.

Каждый раз, когда вы создаете новый объект finalize, JVM создает дополнительный объект FinalizerRef (который чем-то похож на ссылки Weak/Soft/Phantom).

Как только ваш объект становится недостижимым с сильными ссылками, обрабатываются специальные ссылки на этот объект. FinalizerRef для вашего объекта будет добавлен в очередь финализатора (это связанный список, как и для других типов ссылок).

Когда поток финализатора использует FinalizerRef из очереди, он обнуляет свой нулевой указатель на объект (хотя поток будет сохранять сильную ссылку на объект, пока финализатор не завершит работу).

Как только FinalizerRef обнуляется, объект больше не может попасть в очередь финализатора.

Кстати

Вы можете увидеть время обработки предпочтений (и количество ссылок) в журналах GC с помощью -XX:+PrintReferenceGC (см. дополнительные параметры JVM для диагностики GC)

person Alexey Ragozin    schedule 17.02.2014
comment
Спасибо. Это именно то, что я искал. Я исследовал эти классы Finalizer и FinalReference и теперь четко понимаю, как это работает - person Anton Kasianchuk; 21.02.2014

JVM хранит метаданные в заголовке объекта. Вызывается любой объект с подклассами finalize(), даже если он пустой. Постановка в очередь не занимает много времени, но может ждать в очереди долго.

person Peter Lawrey    schedule 16.02.2014
comment
Я могу поверить, что JVM может изменить заголовок объекта, чтобы пометить его как FINALIZED. Но я проводил расследование и увидел ситуацию, когда объект с пустой finalize() удалялся сразу после одной сборки мусора. Я пытаюсь сказать, что если бы этот объект был помещен в очередь для окончательной обработки, потребовалось бы удалить как минимум две сборки мусора (одна сборка мусора - чтобы поместить объект в очередь, вторая сборка мусора - чтобы удалить объект после он ЗАВЕРШЕН). И действительно, когда я добавил System.out.println(I'm finalized) внутри finalize(), этот объект был удален после двух сборок мусора. - person Anton Kasianchuk; 16.02.2014
comment
@AntonKasyanchuk Возможно, вы правы в том, что JVM может сократить этот процесс, но я бы не стал предполагать, что это всегда так. - person Peter Lawrey; 16.02.2014
comment
Спецификация не требует, но настоятельно рекомендует обнаруживать «тривиальные финализаторы», которые включают в себя пустые методы и методы, состоящие исключительно из вызова super.finalize() (завершающего Object.finalize())… См. JLS §12.6. Завершение экземпляров класса, в конце - person Holger; 03.05.2016

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

(в новом)

object.metadata.is_finalized=NEEDS_FINALIZE;

(in gc)

while ((object=findUnreachableObject())!=null) {
    if (object.metadata.is_finalized==NEEDS_FINALIZE) {
        if (hasNonNullBody(object.finalize)) {
            Finalizer.addForProcessing(object);
            object.metadata.is_finalized=IN_FINALIZER_QUEUE;
        } else {
            object.metadata.is_finalized=REMOVE_NOW;
        }
    }
    if (object.metadata.is_finalized==REMOVE_NOW) {
        // destroy the object and free the memory
    }
}

(в финализаторе)

while ((object=getObjectForProcessing)!=null) {
    object.finalize();
    object.metadata.is_finalized=REMOVE_NOW;
}
person Guntram Blohm    schedule 16.02.2014
comment
Проблема в том, что в современном сборщике мусора нет ничего похожего на findUnreachableObject. Единственное, что вы могли бы сделать, это findReachableObject, переместить его в зону выживших и просто забыть весь недостижимый мусор. Поскольку большинство новых объектов умирают молодыми, это намного эффективнее. - person maaartinus; 17.02.2014