17

I thought I knew the answer to this, but I can't find any confirmation after an hour or so of searching.

In this code:

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());
            }
        });
    }
}

Assume that registerCallback saves its parameter somewhere, so that the object of the anonymous subclass will live for a while. Obviously this object has to maintain a reference to obj1 so that onEvent will work if it is called.

But given that the object doesn't use obj2, does it still maintain a reference to obj2, so that obj2 can't be garbage-collected while the object lives? I was under the impression that all visible final (or effectively final) local variables and parameters were captured and thus couldn't be GC'ed as long as the object was alive, but I can't find anything that says one way or the other.

Is it implementation-dependent?

Is there a section in the JLS that answers this? I wasn't able to find the answer there.

ajb
  • 31,309
  • 3
  • 58
  • 84
  • How do you know that `obj2` is bound to `callback$x`? You have seen it in bytecode? – Antoniossss Nov 08 '18 at 08:11
  • 1
    "Is it implementation-dependent?" Technically, yes. There is no reason for the anonymous class to capture `obj2`, but there is no reason it can't. – Andy Turner Nov 08 '18 at 08:17
  • I think a good reason it would not capture `obj2` as a matter of course is that you can have multiple anonymous classes declared in a method: one of those classes might refer to `obj1` only, whilst another might refer to `obj2` only. It wouldn't be sensible for both classes to capture both variables. – Andy Turner Nov 08 '18 at 08:35
  • You can check this using reflection or a debugger. – Peter Lawrey Nov 08 '18 at 10:02

4 Answers4

12

Only obj1 is captured.

Logically, the anonymous class is implemented as a normal class something like this:

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());
    }
});

Note that an anonymous class is always an inner class, so it will always maintain a reference to the outer class, even if it doesn't need it. I don't know if later versions of the compiler have optimized that away, but I don't think so. It is a potential cause of memory leaks.

The use of it becomes:

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

As you can see, the reference value of obj1 is copied (pass-by-value).

There is technically no reason for obj1 to be final, whether declared final or effectively final (Java 8+), except that if it wasn't and you change the value, the copy wouldn't change, causing bugs because you expected the value to change, given that the copying is a hidden action. To prevent programmer confusion, they decided that obj1 must be final, so you can never become confused about that behavior.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 2
    Worth mentioning that since Java 8 the restriction on having to explicitly declare `final` has been lifted. The JLS now talks about "effectively `final`" variables. – Boris the Spider Nov 08 '18 at 08:15
  • it is not only *logically*, it is almost exactly that what the compiler does (Java 8 and 11), just the fields are named differently [:-) – user85421 Nov 08 '18 at 08:41
  • 3
    Note `this` of the enclosing instance is also captured whether it is used or not. – Peter Lawrey Nov 08 '18 at 10:00
  • @PeterLawrey Already covered in the answer (`_outer`), as explained in the note below the class. – Andreas Nov 08 '18 at 17:01
  • @CarlosHeuberger And the class itself is named differently, using a name that is not allowed by Java syntax (e.g. `Outer$1`). – Andreas Nov 08 '18 at 17:07
  • @Andreas I forgot that, but actually it is allowed by Java Language Specification... `$` is a "Java letter", that is, valid as part of an Identifier, just not *recommended*: "The dollar sign should be used only in mechanically generated source code or, rarely, to access pre-existing names on legacy systems". *Interesting* class name, just $ – user85421 Nov 08 '18 at 18:37
  • @CarlosHeuberger Actually, the class name is `1`, which is not a valid Identifier. The `Outer$` prefix is part of the *qualified* name, because an anonymous class is a nested class. – Andreas Nov 08 '18 at 19:00
  • @Andreas I (and JVM) think the class name is `Outer$1`, or what is the class name for just `$`? I also can declare a class `Outer$1` without having any `Outer`. Pleases consult [6.2. Names and Identifiers](https://docs.oracle.com/javase/specs/jls/se11/html/jls-6.html#jls-6.2) (*A qualified name consists of a name, a "." token, and an identifier*) and [JLS 3.8. Identifiers](https://docs.oracle.com/javase/specs/jls/se11/html/jls-3.html#jls-3.8) (part included in my previous comment). – user85421 Nov 08 '18 at 22:09
  • and also [JLS 13.1. The Form of a Binary](https://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1): "*The binary name of an anonymous class (§15.9.5) consists of the binary name of its immediately enclosing type, followed by $, followed by a non-empty sequence of digits.*" – user85421 Nov 08 '18 at 22:18
12

The language spec has very little to say about how anonymous classes should capture variables from their enclosing scope.

The only especially relevant section of the language spec that I can find is JLS Sec 8.1.3:

Any local variable, formal parameter, or exception parameter used but not declared in an inner class must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.)

(Anonymous classes are inner classes)

It does not specify anything about which variables the anonymous class should capture, or how that capturing should be implemented.

I think it is reasonable to infer from this that implementations need not capture variables that aren't referenced in the inner class; but it doesn't say they can't.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
2

I was curious and surprised by your statement that much (why would compiler do such thing???), that I had to check it myself. So I made simple example like this

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);
            }
        };
    }
}

And resulted with following bytecode for of 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;

Which means:

  • L0 - store first final with idx 1 (ASTORE 1)
  • L1 - store second final with idx 2(that one is not used in anon class) (ASTORE 2)
  • L2 - create new test$1 with argumets (ALOAD 0) this and obj1 (ALOAD 1)

So I have no idea, how did you get to the conclusion that obj2 is passed to anonymous class instance, but it was simply wrong. IDK if it is compiler dependent, but as for what other has stated, it is not impossible.

Antoniossss
  • 31,590
  • 6
  • 57
  • 99
  • To answer your question "How did I get to the conclusion"... I thought I had read it somewhere, a long time ago, but it looks like I misremembered. I envisioned a compiler creating a hidden object with a reference to the outer `this` and references to all the `final` variables and parameters, when it first starts processing the inner class. Perhaps I created a mental picture to help me understand what was going on with inner classes, and later got it confused with something I thought I read? I don't know. – ajb Nov 08 '18 at 14:56
0

obj2 will be garbage collected since it has no reference to it. obj1 will not be garbage collected as long as the event is active since even if you created an anonymous class, you've created a direct reference to obj1.

The only thing final does is that you can't re-define the value, it doesn't protect the object from the garbage collector

Nertan Lucian
  • 362
  • 4
  • 16
  • 1
    If you don't understand the OP why would you post an answer to this question when it is already answered by 4 different people. – SamHoque Nov 08 '18 at 08:34
  • OP meant that he thought that even `obj2` is not used in anon class it still holds reference to it (which is false btw). Its not about final or not declarations. – Antoniossss Nov 08 '18 at 08:34
  • I answered base on title, obj2 in op post has no outside reference as long as Outer class is not instantiated – Nertan Lucian Nov 08 '18 at 08:37
  • 1
    You are missing the point and if you *answered base on title*, it is still off topic IMHO. – Antoniossss Nov 08 '18 at 08:37