Все ли окончательные переменные фиксируются анонимными классами? - PullRequest
0 голосов
/ 08 ноября 2018

Мне показалось, что я знаю ответ на этот вопрос, но я не могу найти подтверждения примерно через час поиска.

В этом коде:

public class Outer {

    // other code

    private void method1() {
        final SomeObject obj1 = new SomeObject(...);
        final SomeObject obj2 = new SomeObject(...);
        someManager.registerCallback(new SomeCallbackClass() {
            @Override
            public void onEvent() {
                 System.out.println(obj1.getName());
            }
        });
    }
}

Предположим, что registerCallback где-то сохраняет свой параметр, так что объект анонимного подкласса будет жить некоторое время. Очевидно, что этот объект должен поддерживать ссылку на obj1, чтобы onEvent работал, если он вызывается.

Но, учитывая, что объект не использует obj2, он все еще поддерживает ссылку на obj2, так что obj2 нельзя собирать мусор, пока объект живет? У меня сложилось впечатление, что все видимые final (или эффективно окончательные) локальные переменные и параметры были захвачены и, таким образом, не могли быть GC'связаны, пока объект был жив, но я не могу найдите что-нибудь, что говорит так или иначе.

Это зависит от реализации?

Есть ли в JLS раздел, который отвечает на это? Я не смог найти ответ там.

Ответы [ 4 ]

0 голосов
/ 08 ноября 2018

obj2 будет собирать мусор, так как на него нет ссылок. obj1 не будет собирать мусор, пока событие активно, поскольку даже если вы создали анонимный класс, вы создали прямую ссылку на obj1.

Единственное, что делает final, это то, что вы не можете переопределить значение, оно не защищает объект от сборщика мусора

0 голосов
/ 08 ноября 2018

Я был удивлен и удивлен вашим утверждением о том, что (почему компилятор делает такие вещи ???), что я должен был проверить это сам. Итак, я сделал простой пример, подобный этому

public class test {
    private static Object holder;

    private void method1() {
        final Object obj1 = new Object();
        final Object obj2 = new Object();
        holder = new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println(obj1);
            }
        };
    }
}

И привел следующий байт-код для method1

 private method1()V
   L0
    LINENUMBER 8 L0
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init> ()V
    ASTORE 1
   L1
    LINENUMBER 9 L1
    NEW java/lang/Object
    DUP
    INVOKESPECIAL java/lang/Object.<init> ()V
    ASTORE 2
   L2
    LINENUMBER 10 L2
    NEW test$1
    DUP
    ALOAD 0
    ALOAD 1
    INVOKESPECIAL test$1.<init> (Ltest;Ljava/lang/Object;)V
    PUTSTATIC test.holder : Ljava/lang/Object;

Что означает:

  • L0 - сохранить первый финал с idx 1 (ASTORE 1)
  • L1 - сохранить второй финал с idx 2 (этот не используется в классе anon) (ASTORE 2)
  • L2 - создать новый тест $ 1 с аргументами (ALOAD 0) this и obj1 (ALOAD 1)

Так что я понятия не имею, как вы пришли к выводу, что obj2 передается экземпляру анонимного класса, но это было просто неправильно. IDK, если это зависит от компилятора, но что касается других, это не невозможно.

0 голосов
/ 08 ноября 2018

В спецификации языка мало что сказано о том, как анонимные классы должны захватывать переменные из своей области действия.

Единственный особо важный раздел языковой спецификации, который я могу найти, это JLS Sec 8.1.3 :

Любая локальная переменная, формальный параметр или параметр исключения, используемые, но не объявленные во внутреннем классе, должны быть либо объявлены как final, либо быть фактически окончательными (§4.12.4), либо при попытке использования возникает ошибка времени компиляции. )

( Анонимные классы являются внутренними классами )

В нем ничего не говорится о том, какие переменные должен захватывать анонимный класс или как этот захват должен быть реализован.

Я думаю, что из этого следует сделать вывод, что реализации не должны захватывать переменные, на которые нет ссылок во внутреннем классе; но это не говорит, что они не могут.

0 голосов
/ 08 ноября 2018

Только obj1 захвачено.

Логически , анонимный класс реализован как обычный класс примерно так:

class Anonymous1 extends SomeCallbackClass {
    private final Outer _outer;
    private final SomeObject obj1;
    Anonymous1(Outer _outer, SomeObject obj1) {
        this._outer = _outer;
        this.obj1 = obj1;
    }
    @Override
    public void onEvent() {
         System.out.println(this.obj1.getName());
    }
});

Обратите внимание, что анонимный класс всегда является внутренним классом, поэтому он всегда будет поддерживать ссылку на внешний класс, даже если он ему не нужен. Я не знаю, оптимизировали ли это более поздние версии компилятора, но я так не думаю. Это потенциальная причина утечек памяти.

Использование становится:

someManager.registerCallback(new Anonymous1(this, obj1));

Как видите, справочное значение obj1 равно скопировано (передача по значению).

Технически нет причин для obj1 быть окончательным, независимо от того, объявлено ли final или эффективно окончательно (Java 8+), за исключением того, что если это не так и вы меняете значение, копирование не изменится, что приведет к ошибкам, потому что вы ожидали, что значение изменится, учитывая, что копирование является скрытым действием. Чтобы избежать путаницы программистов, они решили, что obj1 должно быть окончательным, поэтому вы никогда не запутаетесь в этом поведении.

...