Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8271717

Omit enclosing instance fields from inner classes that don't use it

XMLWordPrintable

    • Icon: CSR CSR
    • Resolution: Approved
    • Icon: P3 P3
    • 18
    • tools
    • None
    • behavioral
    • minimal
    • Hide
      As discussed in https://mail.openjdk.java.net/pipermail/amber-spec-experts/2021-July/003063.html, the behavioural compatibility risk concerns programs that use reflection to access the field. The field is a private and synthetic implementation detail of the compiler, code shouldn't make assumptions about its name or that it exists. For code that was relying on reflection to access this$ fields, it is possible to force javac to emit the field by adding an explicit reference to enclosing instance state.
      Show
      As discussed in https://mail.openjdk.java.net/pipermail/amber-spec-experts/2021-July/003063.html, the behavioural compatibility risk concerns programs that use reflection to access the field. The field is a private and synthetic implementation detail of the compiler, code shouldn't make assumptions about its name or that it exists. For code that was relying on reflection to access this$ fields, it is possible to force javac to emit the field by adding an explicit reference to enclosing instance state.
    • Class file construct
    • Implementation

      Summary

      This change causes javac to omit the synthetic field that holds a reference to the enclosing instance of an inner class, if the inner class doesn't capture any state from its enclosing instance.

      Problem

      When javac translates an inner class (a non-static member class, or an anonymous class in a non-static context), it generates a synthetic field to hold a reference to the enclosing instance. That is:

      class T {
        int x;
        class I {
          void f() {
            System.err.println(x);
          }
        }
      }

      is translated as:

      class T {
        int x;
      }
      
      class T$I {
        final T this$0;
        T$I(T t) {
          this$0 = t;
        }
        void f() {
          System.err.println(this$0.x);
        }
      }

      However the this$0 field is always emitted, even if the inner class doesn't capture any enclosing instance state and the field is unused.

      The unused this$0 fields are a source of memory leaks if the state referenced by the enclosing instance is otherwise unused.

      Additionally, the extra fields increase footprint and removing them may improve performance.

      Solution

      The solution is for javac to track whether a particular this$0 field is used when translating inner classes, and omit unused fields.

      For example, consider the following program.

      class T {
        class I {
        }
      }

      The inner class is currently translated as:

      class T$I
        minor version: 0
        major version: 62
        flags: (0x0020) ACC_SUPER
        this_class: #2                          // T$I
        super_class: #8                         // java/lang/Object
        interfaces: 0, fields: 1, methods: 1, attributes: 3
      ...
      {
        final T this$0;
          descriptor: LT;
          flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
      
        T$I(T);
          descriptor: (LT;)V
          flags: (0x0000)
          Code:
            stack=2, locals=2, args_size=2
               0: aload_0
               1: aload_1
               2: putfield      #1                  // Field this$0:LT;
               5: aload_0
               6: invokespecial #7                  // Method java/lang/Object."<init>":()V
               9: return
            LineNumberTable:
              line 2: 0
      }
      SourceFile: "T.java"
      NestHost: class T
      InnerClasses:
        #22= #2 of #19;                         // I=class T$I of class T

      The proposed change is to omit the this$0 field, and instead translate the inner class as:

      class T$I
        minor version: 0
        major version: 62
        flags: (0x0020) ACC_SUPER
        this_class: #7                          // T$I
        super_class: #2                         // java/lang/Object
        interfaces: 0, fields: 0, methods: 1, attributes: 3
      ...
      {
        T$I(T);
          descriptor: (LT;)V
          flags: (0x0000)
          Code:
            stack=1, locals=2, args_size=2
               0: aload_0
               1: invokespecial #1                  // Method java/lang/Object."<init>":()V
               4: return
            LineNumberTable:
              line 2: 0
      }
      SourceFile: "T.java"
      NestHost: class T
      InnerClasses:
        #18= #7 of #15;                         // I=class T$I of class T

      The change to javac's code generation will only be enabled by default for --release 18 and newer target levels, to provide behavioral compatibility for users depending on this implementation detail who upgrade to the latest JDK version and continue to target earlier language levels.

      Specification

      The proposed change to the implementation of javac can be seen in https://git.openjdk.java.net/jdk/pull/4966.

      The use of the synthetic this$0 fields is an implementation detail of javac. The details of inner classes and anonymous classes that are mandated by the specification are unaffected by this change.

      The form of an anonymous class's constructor is mandated by JLS 15.9.5.1. Similarly, for an inner class that is a member (rather than anonymous), the form of its constructor is mandated by JLS 8.8.1. This change does not affect the constructor parameters: the mandated formal parameter corresponding to the enclosing instance will still be included in the constructor signature; the constructor will simply ignore the parameter instead of saving it to a this$0 field if no state from the enclosing instance is captured.

      JLS 13.1 requires every nested class to have "a symbolic reference to its immediately enclosing class or interface". This requirement is fulfilled by the InnerClasses attribute and the NestHost attribute, which both cause constant pool entries of kind CONSTANT_Class_info that serve as symbolic references to the enclosing class T.

            cushon Liam Miller-Cushon
            cushon Liam Miller-Cushon
            Alex Buckley
            Votes:
            0 Vote for this issue
            Watchers:
            10 Start watching this issue

              Created:
              Updated:
              Resolved: