Skip to content

Latest commit

 

History

History
152 lines (134 loc) · 7.79 KB

ClassChains.md

File metadata and controls

152 lines (134 loc) · 7.79 KB

AOT Class Chains

AOT Class Chains are a means to allow the AOT infrastructure to determine if a class in the current JVM instance is the same as a class in a previous JVM instance. This is needed to ensure that a method that gets AOT compiled in one JVM instance can be AOT loaded in a different JVM instance.

Background

In OpenJ9, there exist J9Classes and J9ROMClasses; the former are known as "ramClass"es and the latter are known as "romClass"es. The ramclass is a hierarchy of romclasses. The romclass corresponds roughly to the contents of a .class file; it is not a complete class because it only references its superclass and implemented interfaces by name.

The Shared Class Cache (SCC) only stores romclasses. However, when performing an AOT load, the AOT infrastructure needs to ensure that a relevant J9Class in one instance of a JVM is the same J9Class from a different JVM instance. This is because the J9Class provides a Java Class' entire hierarchy, starting from itself going up to java/lang/Object. The way OpenJ9 validates two ramclasses from different JVM instances is by using Class Chains.

Class Chains

Consider the following:

class B extends A {
    ...
    void foo() {
        ....
    }
}

class C extends B {
    ...
    void foo() {
        D d = ...;
        d.bar();
    }
}

In one JVM instance, the classes A', B', and C' might be loaded, where C' extends B' extends A'. In another JVM instance, the classes A'', B'', and C'' might be loaded, where C'' extends B'' extends A''. The SCC can provide information about whether, romclass(C'') == romclass(C'), romclass(B'') == romclass(B'), and romclass(A'') == romclass(A'). Ignorning interfaces for the moment, if all of these relationships are true, then J9Class(C'') == J9Class(C'). This means that objects of C'' or C' have the same shape, and any relationships known between J9Class(C') and J9Class(B') are also true between J9Class(C'') and J9Class(B'').

Class Chains are this set of romclass relationships, and they are stored in the SCC. They are a list of romclasses; one first walks up the super class chain up to java/lang/Object, and then walks the class' iTable to get the full list of interface classes that are implemented. During a compilation, the JIT creates the list in the order described above and stores it into the SCC. Each romclass is encoded as an offset from the beginning of the SCC. The offsets of various class chains in the SCC are referenced in the various Relocation Records, which describe the list of J9Classes an AOT compilation has asked questions about.

When AOT code, generated in one JVM instance, is loaded into a different JVM instance, the AOT infrastructure processes the Relocation Records; it finds a candidate J9Class pointer in the current JVM and walks its superclasses and interfaces, checking each one's romclass against the one in the Class Chain stored in the SCC. Any mismatch implies the classes might be different, in which case the AOT load fails. In the case of inlined methods however, the AOT load doesn't necessarily fail, rather, the AOT infrastructure disables a particular inlined call site.

Finding a candidate J9Class

For classes that are directly referenced in a method's code, there is a Constant Pool entry in the romclass. For example, in C.foo() there is a call to D.bar(); if D.bar() is a static call, there will be an entry in romclass(C)'s constant pool containing a reference to romclass(D). The VM can directly be asked what J9Class that constant pool entry would resolve to. If that class has already been loaded and is visible to class C, the VM will return the J9Class for class D directly, and this can be validated by a constant pool index. Strictly speaking, this does not require checking the class chains, but in AOT w/ SVM, this is done anyway for consistency.

On the other hand, finding a candidate class that was, during compilation, acquired from profiling, is non-trivial. In the example above, d, a reference of type D, may be a reference to an object of type D, or it may be a reference to some subclass of D (eg. E extends D) that isn't directly referenced by C. In this case, due to information from the IProfiler, it might be known that E is the primary class of the D that's returned. In that case, the JIT will insert a Profiled Inlined Guard that ensures that d.class == E before executing the inlined code.

Because Java uses Class Loaders to load classes, two class loaders can load the same "C extends B extends A" class twice, and this will result in two different J9Class pointers; the difference comes from the fact the two class loaders are different. Frameworks such as OSGI use different class loaders for different modules, and Java applications can use several OSGI modules. Even if there weren't multiple instances of a particular class in the current JVM instance, having to go through every possible class loader to check if it already has that particular class loaded would be extremely slow. If there were multiple instances, then the AOT infrastructure would additionally need to validate every single one. All this would effectively defeat the purpose of AOT code, which is to facilitate fast application startup.

Finding a particular J9Class in the current JVM instance requires a Class Loader and a Class Name. The Class Name can be obtained from the top romclass in the class chain. However, finding the Class Loader is not as simple since they are just Java Objects created by a Java application. OpenJ9 solves this problem by building hash tables that map a) Class Loaders to the first Class that loader loaded, and b) the first class that loader loaded to the Class Loader Object. Regarding the "first class that loader loaded", what actually gets stored is an offset to the class chain for that class. These tables are not persisted; they are built up via the Class Load Hook in the JIT in the current JVM instance. When referencing a particular J9Class in a Relocation Record, the JIT stores 1) the offset to the class chain for the current J9Class itself, as well as 2) the offset to the class chain for the first class loaded by the loader that loaded the current J9Class.

During an AOT load, the AOT infrastructure takes 1) and adds to the current address of the SCC in the current JVM instance to find the class chain to be validated. This class chain provides the name of the class. The AOT infrastructure then uses 2) to get the Class Loader Object in the current JVM instance, which will likely give the correct Class Loader. It then asks the Class Loader to provide the J9Class pointer corresponding to the Class Name. It is worth noting that this J9Class may not yet be loaded. The validation is then performed using this J9Class pointer, if it exists.