Может ли 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 объекту, прежде чем он выходит из области видимости?
Примечание: я знаю, что делать что-то внутри финализаторов плохо. Это код, который я унаследовал и собираюсь исправить - вопрос в том, правильно ли я понимаю проблему с корнем. Если это невозможно, то корень моей ошибки должен быть чем-то более тонким.
2 ответа
Может ли 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
в теме.
Могут быть разработаны оптимизирующие преобразования программы, которые уменьшают число достижимых объектов до меньшего, чем те, которые наивно считаются достижимыми. Например, Java-компилятор или генератор кода может выбрать установку переменной или параметра, который больше не будет использоваться для обнуления, чтобы в будущем хранилище для такого объекта могло быть потенциально восстановимо.
Так что да, я думаю, что для компилятора допустимо добавлять скрытый код для установки a
в null
что позволяет собирать мусор. Если это то, что происходит, вы не сможете определить это по байт-коду (см. Комментарий @user2357112).
Возможный (некрасивый) обходной путь: Добавить public static boolean alwaysFalse = false;
в основной класс или какой-то другой класс, а затем в конце main()
, добавлять if (alwaysFalse) System.out.println(a);
или что-то еще, что ссылки a
, Я не думаю, что оптимизатор может когда-либо с уверенностью определить, что alwaysFalse
никогда не устанавливается (поскольку некоторый класс всегда может использовать отражение, чтобы установить его); следовательно, он не сможет сказать, что a
больше не нужен. По крайней мере, этот вид "обходного пути" может быть использован, чтобы определить, действительно ли это проблема.