2020-04-04

Java Hidden Classes

Hidden Classes

Hidden classes are classes that cannot be used directly by the bytecode of other classes. Hidden classes are intended for use by frameworks that generate classes at run time and use them indirectly, via reflection. A hidden class may be defined as a member of an access control nest, and may be unloaded independently of other classes.

Creating a hidden class

Whereas a normal class is created by invoking ClassLoader::defineClass, a hidden class is created by invoking Lookup::defineHiddenClass. This causes the JVM to derive a hidden class from the supplied bytes, link the hidden class, and return a lookup object that provides reflective access to the hidden class. The invoking program should store the lookup object carefully, for it is the only way to obtain the Class object of the hidden class.

The major difference in how a hidden class is created lies in the name it is given. A hidden class is not anonymous. It has a name that is available via Class::getName and may be shown in diagnostics (such as the output of java -verbose:class), in JVM TI class loading events, in JFR events, and in stack traces. However, the name has a sufficiently unusual form that it effectively makes the class invisible to all other classes. The name is the concatenation of:

The binary name in internal form (JVMS 4.2.1) specified by this_class in the ClassFile structure, say A/B/C;

The '.' character; and

An unqualified name (JVMS 4.2.2) that is chosen by the JVM implementation to be unique in this execution of the JVM.

For example, if this_class specifies com/example/Foo (the internal form of the binary name com.example.Foo), then a hidden class derived from the ClassFile structure may be named com/example/Foo.1234. This string is neither a binary name nor the internal form of a binary name.

Given a hidden class whose name is A/B/C.x, the result of Class::getName is the concatenation of:

The binary name A.B.C (obtained by taking A/B/C and replacing each '/' with '.');
The '/' character; and
The unqualified name x.
For example, if a hidden class is named com/example/Foo.1234, then the result of Class::getName is com.example.Foo/1234. Again, this string is neither a binary name nor the internal form of a binary name.

Hidden classes and class loaders

Despite the fact that a hidden class has a corresponding Class object, and the fact that a hidden class's supertypes are created by class loaders, no class loader is involved in the creation of the hidden class itself. Notice that this JEP never says that a hidden class is "loaded". No class loaders are recorded as initiating loaders of a hidden class, and no loading constraints are generated that involve hidden classes. Consequently, hidden classes are not known to any class loader: A symbolic reference in the run-time constant pool of a class D to a class C denoted by N will never resolve to a hidden class for any value of D, C, and N. The reflective methods Class::forName, ClassLoader::findLoadedClass, and Lookup::findClass will not find hidden classes.

Notwithstanding this detachment from class loaders, a hidden class is deemed to have a defining class loader. This is necessary to resolve types used by the hidden class's own fields and methods. In particular, a hidden class has the same defining class loader, runtime package, and protection domain as the lookup class, which is the class that originally obtained the lookup object on which Lookup::defineHiddenClass is invoked.

Using a hidden class

Lookup::defineHiddenClass returns a Lookup object whose lookup class is the newly created hidden class. A Class object can be obtained for the hidden class by invoking Lookup::lookupClass on the returned Lookup object. Via the Class object, the hidden class can be instantiated and its members accessed as if it was a normal class, except for four restrictions:

Class::getName returns a string that is not a binary name, as described earlier.

Class::getCanonicalName returns null, indicating the hidden class has no canonical name. (Note that the Class object for an anonymous class in the Java language has the same behavior.)

Final fields declared in a hidden class are not modifiable. Field::set and other setter methods on a final field of a hidden class will throw IllegalAccessException regardless of the field's accessible flag.

The Class object is not modifiable by instrumentation agents, and cannot be redefined or retransformed by JVM TI agents. We will, however, extend JVM TI and JDI to support hidden classes, such as testing whether a class is hidden, including hidden classes in any list of "loaded" classes, and sending JVM TI events when hidden classes are created.

Hidden classes in stack traces

Methods of hidden classes are not shown in stack traces by default. They represent implementation details of language runtimes, and are never expected to be useful to developers diagnosing application issues. However, they can be included in stack traces via the options -XX:+UnlockDiagnosticVMOptions -XX:+ShowHiddenFrames.

There are three APIs which reify stack traces: Throwable::getStackTrace, Thread::getStackTrace and the newer StackWalker API introduced in Java 9. For the Throwable::getStackTrace and Thread::getStackTrace API, stack frames for hidden classes are omitted by default; they can be included with the same options as for stack traces above. For the StackWalker API, stack frames for hidden classes should be included by a JVM implementation only if the SHOW_HIDDEN_FRAMES option is set. This allows stack-trace filtering to omit unnecessary information when developers are diagnosing application issues.

Unloading hidden classes

A class defined by a class loader has a strong relationship with that class loader. In particular, every Class object has a reference to the ClassLoader that defined it. This tells the JVM which loader to use when resolving symbols in the class. One consequence of this relationship is that a normal class cannot be unloaded unless its defining loader can be reclaimed by the garbage collector (JLS 12.7). Being able to reclaim the defining loader implies there are no live references to the loader, which in turn implies there are no live references to any of the classes defined by the loader. (Such classes, if they were reachable, would refer to the loader.) This widespread lack of liveness is the only state where it is safe to unload a normal class.

Accordingly, to maximize the chance of unloading a normal class, it is important to minimize references to both the class and its defining loader. Language runtimes typically achieve this by creating many class loaders, each dedicated to defining just one class, or perhaps a small number of related classes. When all instances of a class are reclaimed, and assuming the runtime does not hold on to the class loader, both the class and its defining loader can be reclaimed. However, the resulting large number of class loaders is demanding on memory. In addition, ClassLoader::defineClass is considerably slower than Unsafe::defineAnonymousClass according to microbenchmarks.

A hidden class is not created by a class loader and has only a loose connection to the class loader deemed to be its defining loader. We can turn these facts to our advantage by allowing a hidden class to be unloaded even if its notional defining loader cannot be reclaimed by the garbage collector. As long as there are live references to a hidden class -- either to instances of the hidden class, or to its Class object -- then the hidden class keeps its notional defining loader alive so that the JVM can use that loader to resolve symbols in the hidden class. When the last live reference to the hidden class goes away, however, the loader need not return the favor by keeping the hidden class alive.

Unloading a normal class while its defining loader is reachable is unsafe because the loader may later be asked, either by the JVM or or by code using reflection, to reload the class, that is, to load a class with the same name. This can have unpredictable effects when static initializers are run for a second time. There is no such concern about unloading a hidden class, since hidden classes are not created in the same manner. Because a hidden class's name is an output of Lookup::defineHiddenClass, not an input, there is no way to recreate the "same" hidden class that was unloaded previously.

No comments:

Post a Comment