Может ли java завершить работу над объектом, когда он все еще находится в области видимости?
Я изучал ошибку в своем коде, которая, похоже, вызвана каким-то "уродливым" кодом финализатора. Код выглядит примерно так
public class A {
public B b = new B();
@Override public void finalize() {
b.close();
}
}
public class B {
public void close() { /* do clean up our resources. */ }
public void doSomething() { /* do something that requires us not to be closed */ }
}
void main() {
A a = new A();
B b = a.b;
for(/*lots of time*/) {
b.doSomething();
}
}
Я думаю, что происходит то, что a
обнаруживается отсутствие ссылок после второй строки main()
и получает GC'd и завершается потоком финализатора - пока for
цикл все еще выполняется, используя b
пока a
все еще "в области видимости".
Возможно ли это? Разрешено ли java создавать GC для объекта до того, как он выйдет за пределы области видимости?
Примечание: Я знаю, что делать что-либо внутри финализаторов плохо. Это код, который я унаследовал и собираюсь исправить - вопрос в том, правильно ли я понимаю основную проблему. Если это невозможно, то корнем моей ошибки должно быть что-то более тонкое.
Переведено автоматически
Ответ 1
Может ли Java завершить работу над объектом, когда он все еще находится в области видимости?
ДА.
Однако здесь я педантичен. Область - это языковая концепция, которая определяет допустимость имен. Может ли объект быть собран из мусора (и, следовательно, завершен), зависит от того, является ли он достижимым.
В ответе ajb это почти получилось (+ 1), поскольку цитируется важный отрывок из JLS. Однако я не думаю, что это напрямую применимо к ситуации. В JLS § 12.6.1 также говорится:
Достижимый объект - это любой объект, к которому можно получить доступ при любом потенциально продолжающемся вычислении из любого текущего потока.
Теперь рассмотрим это применительно к следующему коду:
class A {
@Override protected void finalize() {
System.out.println(this + " was finalized!");
}
public static void main(String[] args) {
A a = new A();
System.out.println("Created " + a);
for (int i = 0; i < 1_000_000_000; i++) {
if (i % 1_000_000 == 0)
System.gc();
}
// System.out.println(a + " was still alive.");
}
}
В JDK 8 GA это будет завершаться a
каждый раз. Если вы раскомментируете println
в конце, a
никогда не будет завершено.
С помощью println
закомментированного параметра можно увидеть, как применяется правило достижимости. Когда код достигает цикла, поток не может иметь никакого доступа к a
. Таким образом, он недоступен и, следовательно, подлежит доработке и сборке мусора.
Обратите внимание, что имя a
все еще находится в области видимости, потому что можно использовать a
в любом месте внутри заключающего блока - в данном случае main
тело метода - от его объявления до конца блока. Точные правила области видимости описаны в JLS § 6.3. Но на самом деле, как вы можете видеть, область видимости не имеет ничего общего с достижимостью или сборкой мусора.
Чтобы предотвратить сборку мусора для объекта, вы можете сохранить ссылку на него в статическом поле, или, если вы не хотите этого делать, вы можете сохранить его доступным, используя позже тем же методом после трудоемкого цикла. Должно быть достаточно вызвать для него безобидный метод, такой как toString
.
Ответ 2
Можно разработать оптимизирующие преобразования программы, которые уменьшат количество доступных объектов до меньшего, чем те, которые наивно считались бы достижимыми. Например, компилятор Java или генератор кода может присвоить переменной или параметру, который больше не будет использоваться, значение null, чтобы хранилище для такого объекта можно было потенциально восстановить раньше.
Итак, да, я думаю, компилятору допустимо добавлять скрытый код для set a
в null
, таким образом позволяя собирать мусор. Если это то, что происходит, возможно, вы не сможете определить по байт-коду (см. Комментарий @User ).
Возможный (некрасивый) обходной путь: добавить public static boolean alwaysFalse = false;
в основной класс или некоторые другие классы, а затем в конце main()
добавить if (alwaysFalse) System.out.println(a);
или что-то еще, на что ссылается a
. Я не думаю, что оптимизатор когда-либо сможет с уверенностью определить, что alwaysFalse
никогда не устанавливается (поскольку некоторый класс всегда может использовать отражение для его установки); следовательно, он не сможет сказать, что a
больше не нужен. По крайней мере, такой "обходной путь" можно использовать, чтобы определить, действительно ли это проблема.