Может ли 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 в теме.

JLS §12.6.1:

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

Так что да, я думаю, что для компилятора допустимо добавлять скрытый код для установки a в nullчто позволяет собирать мусор. Если это то, что происходит, вы не сможете определить это по байт-коду (см. Комментарий @user2357112).

Возможный (некрасивый) обходной путь: Добавить public static boolean alwaysFalse = false; в основной класс или какой-то другой класс, а затем в конце main(), добавлять if (alwaysFalse) System.out.println(a); или что-то еще, что ссылки a, Я не думаю, что оптимизатор может когда-либо с уверенностью определить, что alwaysFalse никогда не устанавливается (поскольку некоторый класс всегда может использовать отражение, чтобы установить его); следовательно, он не сможет сказать, что a больше не нужен. По крайней мере, этот вид "обходного пути" может быть использован, чтобы определить, действительно ли это проблема.

Другие вопросы по тегам