Do anonymous classes *always* maintain a reference to their enclosing instance?

前端 未结 3 1203
栀梦
栀梦 2020-11-27 17:14

I\'m working with some code where one object, \"foo\", is creating another object, \"bar\", and passing it a Callable. After this foo will return bar, and then

相关标签:
3条回答
  • You can easily turn a nested anonymous-class into a "static" anonymous-class by introducing a static method in your class.

    import java.util.ArrayList;
    
    
    public class TestGC {
        public char[] mem = new char[5000000];
        public String str = "toto";
    
        public interface Node {
            public void print();
        }
    
        public Node createNestedNode() {
            final String str = this.str;
            return new Node() {
                public void print() {
                    System.out.println(str);
                }
            };
        }
    
        public static Node createStaticNode(TestGC test) {
            final String str = test.str;
            return new Node() {
                public void print() {
                    System.out.println(str);
                }
            };
        }
    
        public Node createStaticNode() {
            return createStaticNode(this);
        }
    
        public static void main(String... args) throws InterruptedException {
            ArrayList<Node> nodes = new ArrayList<Node>();
            for (int i=0; i<10; i++) {
                // Try once with createNestedNode(), then createStaticNode()
                nodes.add(new TestGC().createStaticNode());
                System.gc();
                //Thread.sleep(200);
                System.out.printf("Total mem: %d  Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory());
            }
            for (Node node : nodes)
                node.print();
            nodes = null;
            System.gc();
            //Thread.sleep(200);
            System.out.printf("Total mem: %d  Free mem: %d\n", Runtime.getRuntime().totalMemory(), Runtime.getRuntime().freeMemory());
        }
    }
    
    0 讨论(0)
  • 2020-11-27 18:09

    Yes, instances of anonymous inner classes hold on to a reference to their enclosing instances even if these references are never actually used. This code:

    public class Outer {
      public Runnable getRunnable() {
        return new Runnable() {
          public void run() {
            System.out.println("hello");
          }
        };
      }
    }
    

    When compiled with javac generates two class files, Outer.class and Outer$1.class. Disassembling the latter, the anonymous inner class, with javap -c yields:

    Compiled from "Outer.java"
    class Outer$1 extends java.lang.Object implements java.lang.Runnable{
    final Outer this$0;
    
    Outer$1(Outer);
      Code:
       0:   aload_0
       1:   aload_1
       2:   putfield        #1; //Field this$0:LOuter;
       5:   aload_0
       6:   invokespecial   #2; //Method java/lang/Object."<init>":()V
       9:   return
    
    public void run();
      Code:
       0:   getstatic       #3; //Field java/lang/System.out:Ljava/io/PrintStream;
       3:   ldc     #4; //String hello
       5:   invokevirtual   #5; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
       8:   return
    
    }
    

    The putfield line shows that a reference to the enclosing instance is being stored in the field this$0 (of type Outer) by the constructor even though this field is never used again.

    This is unfortunate if you're attempting to create small potentially long-lived objects with anonymous inner classes as they'll hold onto the (potentially large) enclosing instance. A workaround is to use an instance of a static class (or a top-level class) instead. This is unfortunately more verbose.

    0 讨论(0)
  • 2020-11-27 18:15

    The static alternative (in this case) is not much larger (1 line):

    public class Outer {
      static class InnerRunnable implements Runnable {
          public void run() {
            System.out.println("hello");
          }
        }
      public Runnable getRunnable() {
        return new InnerRunnable();
      }
    }
    

    BTW: if you use a Lambda in Java8 there will be no nested class generated. However I am not sure if the CallSite objects which get passed around in that case hold an reference to the outer instance (if not needed).

    0 讨论(0)
提交回复
热议问题