JDK-8268628 : 5.3.5: Properly specify how circularity checking & multi-threading work
  • Type: Bug
  • Component: specification
  • Sub-Component: vm
  • Affected Version: 16
  • Priority: P4
  • Status: Resolved
  • Resolution: Fixed
  • Submitted: 2021-06-11
  • Updated: 2024-06-15
  • Resolved: 2024-06-13
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.
JDK 23
23Fixed
Related Reports
Relates :  
Description
The following rule from 5.3.5 is poorly-specified:

---

Otherwise, if any of the superclasses of *C* is *C* itself, loading throws a `ClassCircularityError`.

---

Since C has not been created yet, it is unclear how we might detect that a superclass is "C itself". A naive approach would try to resolve C, then perform a check, but of course attempting to derive C while deriving C would cause a recursive loop or overflow.

The following reflects actual HotSpot behavior for the check:

---

~~Otherwise, if any of the superclasses of *C* is *C* itself~~ **If the
attempt to resolve the direct superclass leads to the current thread
attempting to recursively derive a class named *N* using loader *L***,
loading  throws a `ClassCircularityError`.

:::editorial
How can we decide whether a superclass is "*C* itself" without actually
creating the superclass first?
And, of course, we can't create the superclass without recursively
applying this check.
This dependency loop is broken by detecting when a second attempt is
made to derive a class named *N*.
:::

---

and then, to complete the picture in the case of multi-threaded loading, added to Step 5:

---

**If, since step 1, in another thread, the Java Virtual Machine has marked a
class or interface named *N* as having *L* as its defining class loader,
that class or interface is the result of class derivation, and the class or
interface derived in steps 1-4 is discarded.**

---

However, it's not clear whether JVMS has a notion of multiple threads of execution in the context of resolution or class loading. Need to explore further. (And perhaps also add something about multi-threaded resolution to 5.4.3.)
Comments
Proposed fix: #### 5.3.5 Deriving a Class from a `class` File Representation {#jvms-5.3.5} **Class loaders require the cooperation of the Java Virtual Machine to derive and create a class or interface from a binary representation provided by the loader ([5.3.1], [5.3.2]).** The following steps are used to derive a ~~nonarray~~ class or interface *C* denoted by *N* from a purported representation in `class` file format ~~using the~~ **when requested by a** class loader *L*. 1. First, the Java Virtual Machine determines whether **the request by class loader *L* to derive a class or interface denoted by *N* is permitted.** **If** *L* has already been recorded as an initiating loader of a class or interface denoted by *N*, ~~If so, this derivation attempt is invalid and~~ derivation throws a `LinkageError`. **If the Java Virtual Machine is already in the process of deriving a class or interface denoted by *N* as requested by class loader *L*, derivation throws a `ClassCircularityError`.** 2. ~~Otherwise~~ **Next**, the Java Virtual Machine attempts to parse the purported representation. ... 3. If *C* has a direct superclass, the symbolic reference from *C* to its direct superclass is resolved using the algorithm of [5.4.3.1], with *L* acting as the defining loader of *C*. Note that if *C* is an interface it must have `Object` as its direct superclass, which must already have been loaded. Only `Object` has no direct superclass. Any exception that can be thrown as a result of failure of class or interface resolution can be thrown as a result of derivation. In addition, derivation must detect the following problems: - ~~If any of the superclasses of *C* is *C* itself, derivation throws a `ClassCircularityError`.~~ - ~~Otherwise, if~~ **If** the class or interface named as the direct superclass of *C* is in fact an interface or a `final` class, derivation throws an `IncompatibleClassChangeError`. ... 4. If *C* has any direct superinterfaces, the symbolic references from *C* to its direct superinterfaces are resolved using the algorithm of [5.4.3.1], with *L* acting as the defining loader of *C*. Any exception that can be thrown as a result of failure of class or interface resolution can be thrown as a result of derivation. In addition, derivation must detect the following problems: - ~~If any of the superinterfaces of *C* is *C* itself, derivation throws a `ClassCircularityError`.~~ - ~~Otherwise, if~~ **If** any class or interface named as a direct superinterface of *C* is not in fact an interface, derivation throws an `IncompatibleClassChangeError`. ... :::inserted Requests to derive a class or interface may be made concurrently by class loader code executing in multiple threads, but the derivation process is sequential. The Java Virtual Machine implementation ensures that only one request by a given class loader to derive a class or interface of a given name is processed at a time, while all other such requests wait until the first request is complete. > As specified by the derivation process, if the first request is successful, > no subsequent requests will be permitted. > The `ClassLoader` API provides mechanisms to synchronize derivation requests > and cache successful results so that redundant derivation requests do not > occur. :::
15-06-2024

The proposed text for handling multiple threads is incorrect: we haven't guaranteed that they are requesting to derive the same class *using the same bytes*. It would be bad to return the same Class to callers who are providing different bytes. In practice, we simple leave it to the ClassLoader to synchronize and cache the results properly so that redundant derivation attempts don't happen. If racing attempts do occur, one wins, and others fail as invalid.
13-06-2024

This fix was originally proposed with sealed classes (https://docs.oracle.com/en/java/javase/16/docs/specs/sealed-classes-jvms.html ), but is being spun off here to better address the use of threads.
11-06-2021