-
Bug
-
Resolution: Unresolved
-
P4
-
24
All signature classes are eagerly loaded when a method is submitted for JIT-compilation. However, Method::load_signature_classes doesn't persist any failures happening during class loading.
src/hotspot/share/oops/method.cpp:
// load everything, including arrays "[Lfoo;"
Klass* klass = ss.as_klass(SignatureStream::ReturnNull, THREAD);
// We are loading classes eagerly. If a ClassNotFoundException or
// a LinkageError was generated, be sure to ignore it.
if (HAS_PENDING_EXCEPTION) {
if (PENDING_EXCEPTION->is_a(vmClasses::ClassNotFoundException_klass()) ||
PENDING_EXCEPTION->is_a(vmClasses::LinkageError_klass())) {
CLEAR_PENDING_EXCEPTION;
It's not a recent change in behavior. The logic was introduced long time ago (as part ofJDK-4963485). But there's an ongoing discussion about whether JVM is allowed to ignore errors during eager class loading in presence of intermittent failures. (In other words, should subsequent loading attempts report the same error?)
And there is a larger issue here that demands some clarification.
When the VM resolves a CONSTANT_Class entry there are clear rules about recording the outcomes, including memoizing errors. The location for that recording is CONSTANT_Class entry itself, as situated in the constant pool that defines it. Several distinct constant pools can store different outcomes, but each entry must be consistent with itself. Other factors such as class loader constraints and the finality of the “define-class” operation, tend to enforce consistency among queries scattered across multiple constant pools.
The missing bit here, though, is what happens when a class must be loaded apart from resolution of a CONSTANT_Class entry. Several parts of the VM perform such loading. Let’s call it “non-resolving loading”. The case of load-signature-classes is non-resolving loading. The verifier does it as well, and so do reflective APIs (when they reify descriptor types as classes, or load annotations). Unlike the verifier, load-signature-classes is not in the VM spec directly, so (to the extent that it is actually correct!) it must be justified indirectly from the spec.
Another missing bit here, which overlaps with non-resolving loading, is the fact that the VM sometimes loads some class on its own “time table”, and specifically before the application requests the loading of that same class directly. A direct request to load a class is a resolution of a CONSTANT_Class entry, which almost always corresponds to some identifiable instruction in the source code of the application. But non-resolving loading actions can correspond only indirectly to the application code. Reflection and verifier operations are somewhat indirect in this way, and the boot sequence of the VM (which loads a bunch of unpredictable classes in java.base) is completely indirect, since it happens before the application main is entered.
We need a clarification of the spec that allows for those indirect load events, and in particular which clearly defines the amount of liberty that the VM has in reordering such events. Let’s call a VM class load event that occurs on the VM’s own "time table”, subject to the permissions given by the VM spec., a “spontaneous load”. It is not spontaneous from the VM’s point of view, because the VM obviously has a good reason for scheduling the load. But it is spontaneous from the point of view of the application and its author.
In the above terms, the loading of signature classes, which includes the subsequent final definition of the class and/or discarding of an error, is both non-resolving and also spontaneous.
Returning to the VM spec., class loads of every kind are required explicitly in some cases, and tacitly permitted (allowed implicitly) in others. The load-signature-classes operation (to the extent that it is not a bug!) is almost certainly the latter.
Tacit permission usually looks something like this:
- The VM decides to shift the loading of a class to a time earlier than its first use by the application.
- From the application point of view, the class appears to be loaded at the moment of first use, except that it happens “very quickly”.
- This may be called an “as if” optimization: The application-visible effects are "as if" the VM had loaded the class, even if the VM and the OS “know” it happened earlier.
- The VM ensures that the class loader used to load the class has no application-visible side effects.
- (Alternatively, some class loader effects, visible to the application, may be declared due to spontaneous loads. But it’s probably better just to not optimize in the presence of an unknown class loader.)
- The application agrees not to snoop the VM or OS to try to detect when the relevant class file was accessed, or when the VM actually executed its class-loading logic.
- (Some “snooping” action, such as JVMTI or reflectively opening up private fields of VM implementation classes, might be viewed as important enough to allow to interfere with the “as if” optimization, causing the optimization to be disabled. But some snooping will always be “below the virtual metal": you will see things your eyes won’t believe, and get over it. This might not apply to JVMTI but surely applies to OS-level tracing and low-level VM logging operations, which can always expose any tricks that the VM is playing.)
One possible way to resolve questions with load-signature-classes is to reframe it as “find-signature-classes”, and then allow "as if” optimizations to shift the loads, as long as there is tacit permission to do so.
This might conceivably cause performance regressions with user-defined class loaders. If so, the debt of fixing such problems might be moved to Project Leyden, which is constituted to create new rules for selectively shifting operations even when an “as if” rule does not apply, due to application-visible effects. But, no such new rules are needed in the most important cases, of types which are hardwired into the JDK and/or loaded via class loaders whose behavior is known to the VM. In those cases, the VM tacitly permits the shifted load, by an appeal to an “as if” optimization.
From this viewpoint, a “find signature classes” operation would be unobjectionable. It can be narrated as “we are opportunistically finding classes that we know already exist.” And it can be extended with an as-if optimization: “…already exist or, by appeal to an as-if optimization, can eventually exist”.
Because it mentions loading, a “load signature classes” has to be footnoted more explicitly: “Any loading done here is justified as an as-if optimization”. Maybe eventually the footnote can be: “The current Leyden rules allow us to issue a load here, as well as pick up classes already in the system dictionary”.
If other spec experts agree with the above reasoning, a suggested fix for the specific issue is to rename the JIT query as find_signature_classes, and go through the spontaneous loading logic putting in gating logic that refrains from touching (invoking methods on) class loaders unknown to the JDK. And perhaps additional gating logic that refrains from touching even the built-in class loaders, if some JVMTI feature is enabled that could (a) observe their behavior or (b) change their behavior.
src/hotspot/share/oops/method.cpp:
// load everything, including arrays "[Lfoo;"
Klass* klass = ss.as_klass(SignatureStream::ReturnNull, THREAD);
// We are loading classes eagerly. If a ClassNotFoundException or
// a LinkageError was generated, be sure to ignore it.
if (HAS_PENDING_EXCEPTION) {
if (PENDING_EXCEPTION->is_a(vmClasses::ClassNotFoundException_klass()) ||
PENDING_EXCEPTION->is_a(vmClasses::LinkageError_klass())) {
CLEAR_PENDING_EXCEPTION;
It's not a recent change in behavior. The logic was introduced long time ago (as part of
And there is a larger issue here that demands some clarification.
When the VM resolves a CONSTANT_Class entry there are clear rules about recording the outcomes, including memoizing errors. The location for that recording is CONSTANT_Class entry itself, as situated in the constant pool that defines it. Several distinct constant pools can store different outcomes, but each entry must be consistent with itself. Other factors such as class loader constraints and the finality of the “define-class” operation, tend to enforce consistency among queries scattered across multiple constant pools.
The missing bit here, though, is what happens when a class must be loaded apart from resolution of a CONSTANT_Class entry. Several parts of the VM perform such loading. Let’s call it “non-resolving loading”. The case of load-signature-classes is non-resolving loading. The verifier does it as well, and so do reflective APIs (when they reify descriptor types as classes, or load annotations). Unlike the verifier, load-signature-classes is not in the VM spec directly, so (to the extent that it is actually correct!) it must be justified indirectly from the spec.
Another missing bit here, which overlaps with non-resolving loading, is the fact that the VM sometimes loads some class on its own “time table”, and specifically before the application requests the loading of that same class directly. A direct request to load a class is a resolution of a CONSTANT_Class entry, which almost always corresponds to some identifiable instruction in the source code of the application. But non-resolving loading actions can correspond only indirectly to the application code. Reflection and verifier operations are somewhat indirect in this way, and the boot sequence of the VM (which loads a bunch of unpredictable classes in java.base) is completely indirect, since it happens before the application main is entered.
We need a clarification of the spec that allows for those indirect load events, and in particular which clearly defines the amount of liberty that the VM has in reordering such events. Let’s call a VM class load event that occurs on the VM’s own "time table”, subject to the permissions given by the VM spec., a “spontaneous load”. It is not spontaneous from the VM’s point of view, because the VM obviously has a good reason for scheduling the load. But it is spontaneous from the point of view of the application and its author.
In the above terms, the loading of signature classes, which includes the subsequent final definition of the class and/or discarding of an error, is both non-resolving and also spontaneous.
Returning to the VM spec., class loads of every kind are required explicitly in some cases, and tacitly permitted (allowed implicitly) in others. The load-signature-classes operation (to the extent that it is not a bug!) is almost certainly the latter.
Tacit permission usually looks something like this:
- The VM decides to shift the loading of a class to a time earlier than its first use by the application.
- From the application point of view, the class appears to be loaded at the moment of first use, except that it happens “very quickly”.
- This may be called an “as if” optimization: The application-visible effects are "as if" the VM had loaded the class, even if the VM and the OS “know” it happened earlier.
- The VM ensures that the class loader used to load the class has no application-visible side effects.
- (Alternatively, some class loader effects, visible to the application, may be declared due to spontaneous loads. But it’s probably better just to not optimize in the presence of an unknown class loader.)
- The application agrees not to snoop the VM or OS to try to detect when the relevant class file was accessed, or when the VM actually executed its class-loading logic.
- (Some “snooping” action, such as JVMTI or reflectively opening up private fields of VM implementation classes, might be viewed as important enough to allow to interfere with the “as if” optimization, causing the optimization to be disabled. But some snooping will always be “below the virtual metal": you will see things your eyes won’t believe, and get over it. This might not apply to JVMTI but surely applies to OS-level tracing and low-level VM logging operations, which can always expose any tricks that the VM is playing.)
One possible way to resolve questions with load-signature-classes is to reframe it as “find-signature-classes”, and then allow "as if” optimizations to shift the loads, as long as there is tacit permission to do so.
This might conceivably cause performance regressions with user-defined class loaders. If so, the debt of fixing such problems might be moved to Project Leyden, which is constituted to create new rules for selectively shifting operations even when an “as if” rule does not apply, due to application-visible effects. But, no such new rules are needed in the most important cases, of types which are hardwired into the JDK and/or loaded via class loaders whose behavior is known to the VM. In those cases, the VM tacitly permits the shifted load, by an appeal to an “as if” optimization.
From this viewpoint, a “find signature classes” operation would be unobjectionable. It can be narrated as “we are opportunistically finding classes that we know already exist.” And it can be extended with an as-if optimization: “…already exist or, by appeal to an as-if optimization, can eventually exist”.
Because it mentions loading, a “load signature classes” has to be footnoted more explicitly: “Any loading done here is justified as an as-if optimization”. Maybe eventually the footnote can be: “The current Leyden rules allow us to issue a load here, as well as pick up classes already in the system dictionary”.
If other spec experts agree with the above reasoning, a suggested fix for the specific issue is to rename the JIT query as find_signature_classes, and go through the spontaneous loading logic putting in gating logic that refrains from touching (invoking methods on) class loaders unknown to the JDK. And perhaps additional gating logic that refrains from touching even the built-in class loaders, if some JVMTI feature is enabled that could (a) observe their behavior or (b) change their behavior.
- relates to
-
JDK-8334888 5.4: Clarify whether/when "spontaneous" class loading is permitted
- Open