JDK-8043190 : 5.5: Trigger interface initialization on default method invocation
  • Type: Bug
  • Component: specification
  • Sub-Component: vm
  • Affected Version: 8
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • Submitted: 2014-05-14
  • Updated: 2017-02-17
  • Resolved: 2015-02-16
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
When a default method is invoked, the static fields of the declaring interface should be initialized.  This condition should be added to the list of circumstances in which initialization can occur.

This condition should _not_ trigger initialization of any superinterfaces or subinterfaces -- only the interface that declares the method.

Note that default methods can be accessed through a variety of paths -- invokevirtual, invokeinterface, invokespecial, and MethodHandles.
To clarify the search order: the suggested "postorder depth-first traversal" terminology is probably insufficient; what is wanted is: - All superinterfaces are considered, left-to-right - For each interface being considered, the search recurs on _its_ superinterfaces - The superinterfaces of an interface are listed before the interface itself

[Edit: this comment can be ignored. We do want a "postorder depth-first traversal", as outlined above (and probably this search should be explicitly defined in JVMS).] The terminology "postorder depth-first traversal" may need to be defined, or approached in a different way. Simply recurring on each superinterface is not acceptable, without some reworking of the whole algorithm, because i) not all superinterfaces should be initialized; ii) interface initialization should not generally recursively trigger initialization of _its_ superinterfaces; and iii) failure of an ancestor interface to initialize should not be interpreted as a failure of a closer ancestor interface to initialize. (Each of these three points are subject to further design discussion, though.)

The existing JVM spec. requires that classes will be initialized starting at four points: get-static, put-static, invoke-static, and new-instance bytecode operations. There are also various corresponding reflective operations and JNI operations which interact with initialization. The previous comment sketched a design where the additional interface initializations (required to fence at least some default-method-carrying interfaces) would occur as a part of new-instance bytecode (or reflective or JNI) operations. Without saying so directly, this design implies the creation of a new sub-type of initialization operation, and even a new initialization-related state for classes to be in. The state "C is initialized" splits into "C is initialized by static member access", and a later state of "C is initialized by instance creation". The splitting of the state amounts to a new externally visible state transition for classes, and requires a distinction to be made for all points in any JVM implementation which detect or change the initialization state of a class. As Dan suggested in his earlier comment, we could keep the existing state structures and initialize super-interfaces. This is slightly more eager than is needed to preserve the integrity of default methods, since logically you can use only the static methods of a type K without ever raising the question of what happens with instances of type K. The pre-existing design of interface initialization may be viewed as encouraging the static-only view. On balance, because it does not require initialization-aware code to make new distinctions, Dan's proposal seems better than mine.

Here is some further discussion on those points. Define the term "defaults-bearing" as meaning an interface that contains at least one non-static, non-abstract method. (At first I wanted to say "bytecode-bearing" but that leads to a slightly different design, because static methods can bear bytecodes just as well as defaults can.) i) If the class C is being initialized because of instance creation (new C), all of its defaults-bearing interfaces must be initialized. So it is reasonable for rule #7 to make a full traversal of super-types (of super-classes too for that matter), and initialize the defaults-bearing ones, described above as [SC, SI1, SIn]. Now, if the class C is being initialized because of some reason other than instance creation (static method reference, or perhaps some reflective operation), then there is no compelling reason to initialize a defaults-bearing super J >: C, until an instance is created later, if ever. Likewise, if a super-interface interface J >: C contains a static method but is not defaults-bearing, there is no compelling reason to initialize J when a new C is created. It would be logically defensible to initialize such J, as long as interfaces which were compiles from codes older than Java 8 were skipped. This means that the JVM must distinguish between two events: initializing C in the course of constructing a new C, vs. initializing C in order to access its static members. This splits the "I have been initialized" state into "I have had my static members used" and "I have been instantiated". In both states, the C.<clinit> method has been run, but only in the latter state are the J.<clinit> methods run. I think all this means that the new language should be inserted into the JVMS page for the "new" opcode (0xBB), as follows: On successful resolution of the class ***C***, it is initialized (��5.5) if it has not already been initialized. ***Let SI1, ..., SIn be all superinterfaces of C that declare at least one non-static, non-abstract method, ordered by a postorder depth-first traversal of the classes' 'interfaces' arrays. ***On successful initialization of C, for each S in the list [ SI1, ..., SIn ], S is initialized (��5.5) if it has not already been initialized.*** Also: Run-time Exception Otherwise, if execution of this new instruction causes initialization of the referenced class ***or any of its superinterfaces***, new may throw an Error as detailed in JLS ��15.9.4. Note that the JVM can be expected to easily produce a list of the so-called "transitive interfaces" of a given class C, since such precomputed sets are useful for laying out dispatch mechanisms (generally speaking, and true in particular of HotSpot). The exact order of such intra-JVM lists, of course, is not naturally determined, and would require attention, but that's not a spec problem. The JVM would traverse these lists, only in the case of instance creation, and would pick out and instantiate the relevant interfaces. ii) Outside of new-instance creation of a class and direct access to an interface's statics, there is no reason to trigger initialization. In neither of those cases are interface-to-interface type relations relevant. (New-instance creation is referred to a class and its transitive closure of supers, while static init of an interface traditionally ignores supers.) Moving the new spec. language as suggested will remove recursive interface-to-interface triggering. iii) I now agree that an interface initialization failure should not cause another type's initialization to fail, directly. The effects of an initialization action of an interface should be limited to that interface, and to whoever requests its initialization. A get/put/invoke/static opcode against an interface J must fail (always with the same error) if J fails to initialize. (We should ignore the possibility that the static reference might have searched through a symbolic reference to some subtype K <: J.) A new-instance opcode against a class C with a defaults-bearing super J >: C must fail if J fails to initialize. Note that C might be already safely initialized when J fails. This must be regarded as normal, since C might have been statically initialized (because of a reference to a static member C.s) long before any "new C" was created. If the "new C" fails due to a failure of J.<clinit>, it must still be possible to execute static code (C.s, etc.), even if C can never be instantiated. Basically, the possibility of a separate failure of J during a "new C" operation must be allowed. It must, of course, take place before any code in C (C.<init>) is executed, so that the blank instance can be gracefully discarded if J's initialization fails.

The suggestion to trigger initialization of an interface when a default method is "invoked" (should be interpreted as "selected for invocation") is impractical to implement. Instead, the initialization should be triggered by initialization of a subclass. Specifically, rule #7 should read: Next, if C is a class rather than an interface, ***let SC be its superclass, and let SI1, ..., SIn be all superinterfaces of C that declare at least one non-static, non-abstract method, ordered by a postorder depth-first traversal of the classes' 'interfaces' arrays. For each S in the list [ SC, SI1, ..., SIn ],*** recursively perform the entire procedure for ***S***. If necessary, verify and prepare ***S*** first. If the initialization of ***S*** completes abruptly because of a thrown exception, then acquire LC, label the class object for C as erroneous, notify all waiting threads, release LC, and complete abruptly, throwing the same exception that resulted from initializing ***S***. (This is a change from the current unspecified behavior of Hotspot, which initializes the interface when it declares _or inherits_ a default method.)