Static compilation alone could not work for the Java applications that load classes unknown at compile time. To cope with it, the JET Runtime includes a just-in-time (JIT) compiler to support dynamic compilation of the Java bytecode. That enables you to statically compile a part of the application and remain the other part "as is" - in the form of .class or .jar files. This technique is called Mixed Compilation Model (MCM.) Though MCM is always enabled in the JET Runtime, dynamic compilation is demand-driven, that is, it occurs when the application attempts to load a class that was not pre-compiled.
A typical use case for MCM would be a Java program whose client installation is extensible with plug-ins. The core of such an application can be pre-compiled and then deployed to target systems as the native executable. If the user installs a plug-in distributed as a .jar file, the JET Runtime loads it using the dynamic compiler. This way, an application backed by MCM is able to correctly load the classes that were not or could not be compiled before execution.
Note, however, that dynamic compilation occurs at run time and competes with the executing program for resources. As a result, it may worsen start-up time and/or performance of the application. If this is the case, you address the problem by configuring the JET Runtime in one of three ways:
This chapter describes each technique in details and also points out certain Java technologies that definitely require Mixed Compilation Model.
Let us consider an example that does not work without dynamic compilation. Since version 1.3, the J2SE platform includes Dynamic Proxy API, a mechanism that allows programs to dynamically create bytecode that represents classes and create new instances of those classes. A dynamic proxy class is a class that implements a list of interfaces specified at run time.
Obviously, Dynamic Proxy API cannot operate without dynamic compilation because bytecodes are created on-the-fly, literally as arrays of bytes, which are then fed to the virtual machine.
You may get convinced of that it works flawlessly with the JET Runtime - just check samples/DynProxy in your JET installation directory.
For certain types of application, the JET Runtime definitely employs the mixed model. That is the case, if some classes to be loaded are not known at the moment of static compilation. In general, there are two reasons for that:
For example, dynamic compilation occurs, if your application features the following:
| Feature | Used by |
| loading classes from remote hosts | RMI Distributed Object Model |
| generation of bytecode at run time | Dynamic Proxy API |
| generation of class files at run time | JSP, XSP |
| custom classloading and third-party plug-ins | RCPs (Eclipse, NetBeans), certain fat clients |
| dynamic deployment of components | J2EE application servers |
Even though certain classes of your application can be compiled statically, you may consider explicit enabling the mixed model for them. It may be useful if your application contains a lot of similar classes but loads just a few. A good example is country-specific or language-specific classes in internationalized applications. Instead of precompiling all of them, you may supply those classes in a .jar file. You may optionally pack that .jar to the executable as described in Resource packing.
However, it is not recommended to use MCM for "hot" classes that take a significant fraction in total execution time. It would be better to pre-compile such classes because the JIT compiler incurs run time overheads and employs less efficient optimizations as compared to the static compiler.
The JET Runtime comes with two JIT compilers:
This JIT compiler, available only in the Professional Edition, is essentially the main static compiler, scaled down and adapted for runtime use.
This compiler implements a few low-cost optimizations, and thus is an order of magnitude faster than the optimizing JIT compiler.
The differences between the two compilers are summarized in the following table:
| Characteristic | Optimizing JIT | Fast JIT |
| Compilation speed | Low (order of magnitude slower than HotSpot) | High (comparable to HotSpot) |
| Code quality | High (somewhat below the static compiler) | Moderate |
| Memory requirements | High | Moderate |
Note: If the optimizing JIT compiler fails to compile a method due to lack of memory or inability to restore the method’s structure suitable for optimizations, which may be caused e.g. by obfuscation, it always invokes the fast JIT compiler for such a method.
You select either optimizing or fast JIT compiler on Options page of the JET Control Panel. Another way to force using a particular JIT is setting the system property jet.jit.optimizing or jet.jit.fast, respectively.
If you do not select the JIT compiler explicitly, the JET Runtime will use the optimizing JIT compiler. /The fast JIT may be enabled by default if you use JIT Cache Optimizer (see How to optimize a JIT cache) or the xjava launcher./
Note: Information in this section is applicable to Excelsior JET Professional Edition only.
To reduce the overhead of dynamic compilation, the JET Runtime is capable of caching the code produced by the JIT compiler during execution. Once a JIT cache is created, it may be reused by the application on next launches so that JIT compiler will not be invoked for the cached classes. To provide correctness, the JET Runtime performs consistency checks to ensure that the bytecode being loaded has not been changed since the previous run. If the checks fail, JIT cache is (partially) rejected, that is, the outdated code is not used. In place of it, the JIT compiler produces an up-to-date version of the cache.
You enable the caching mechanism for your application on Options page of the JET Control Panel or by setting the system property jet.jit.cache.
Technically, JIT cache is organized as a directory that contains a set of shared libraries with the cached code and a cache descriptor file. The JET Runtime consults the cache descriptor when it performs consistency checks or looks for a piece of the cached code.
By default, the the JIT cache directory has the name jittemp and is located in:
the directory which is set current when the executable is launched (if applicable, the Working Directory property of the respective shortcut, otherwise the directory containing the executable file)
the current directory of the calling process at the moment of the first call to JNI_CreateJavaVM()
You may assign a different location for the cache directory on Options page of the JET Control Panel or by setting the system property jet.jit.cache.dir, for instance:
-Djet.jit.cache.dir:/tmp/jitcache
Note: In general, using an absolute pathname for the cache directory is not recommended as it may cause troubles on deployment. If you want to rename the cache directory or change its location on the enduser system, specify the jet.jit.cache.dir property when packaging your application for deployment (see Editing Java properties for details.)
Many JVM implementations, e.g. Sun HotSpot, load classes on the first use (class instantiation, static method call or static field access), not on the first reference. This is called lazy class loading. Therefore, if a referenced class is not available at application launch time but becomes available at run time before it is actually used by a method, the program executes normally.
By default, the JET static compiler resolves all references to classes at compile time so if you create a project that includes classes with unresolved import dependencies, JET Control Panel displays detailed warnings on Classes page. The compiler will report an error on attempt to build such a project.
Sometimes class absence is just an inconsistency that may be easily fixed. For example, it may appear if you forget to add a .jar to the compilation set. However, if you have double checked your project and not found the missing classes in a jar file or directory, you have to postpone compile-time resolution of such classes:
-CLASSABSENCE=HANDLE
As a result, the compiler will generate special stubs at all places where an unavailable class is used. On the first execution, such a stub checks if the referenced class has already been loaded. If not, the stub attempts to load it using the JIT compiler and then uses reflection information (hence the term reflective shield) to resolve the field or method that was referenced in the code. On subsequent executions, the stub will determine that the resolution has already occured and proceed to the actual use of the field or method.
If a shielded class has not become available at run time, the respective stubs will throw java.lang.NoClassDefFoundError.
You may also use this reflective shield facility to pick out the classes and packages that you do not want to precompile by simply making them unavailable to the JET static compiler. However, take into account that reflective stubs create overhead that may lead to considerable performance degradation if methods of the shielded classes are frequently executed.
Note: The JIT compiler is also capable of generating reflective stubs for imported classes that are not available yet.
Note: Information in this section is applicable to Excelsior JET Professional Edition only.
This section describes JIT Cache Optimization, an innovative Excelsior’s technology that enables you to use the JET static compiler to optimize applications that rely on custom classloaders.
Many Java applications use custom classloading to dynamically load classes at run-time. This technique provides unique name spaces for applications that support third-party plug-ins. Each plug-in is loaded by a separate custom classloader thus avoiding a possible class names conflict.
The JET Caching JIT compiler is capable of compiling and caching classes loaded by custom classloaders. The problem, however, is that the dynamic compiler is a scaled down version of the main, ahead-of-time JET compiler and, therefore, performs weaker optimizations in exchange for substantially lower resource demands during on-the-fly compilation. Therefore, the quality of dynamically compiled code is lower.
If an application heavily uses custom classloaders, yet another problem arises. Classloading, as it is defined in the Java Language Specification, imposes strict rules on the order of class lookup and resolution of references to other classes. This makes ahead-of-time compilation of such application impossible, because in a general case not all references may be resolved statically. A simple counterexample is a custom classloader defining its own classpath at run-time. As a result, it may occur that only a small part of the application can be statically compiled even if the majority of its classes is known at compile-time. So the advantages of static compilation for applications of such kind are significanly reduced. The challenge was to work around this problem.
Finally, a JIT cache for a particular application may contain dozens of shared libraries created during multiple dynamic loading sessions. For example, the JIT cache produced for Eclipse IDE v3.1 counts approximately a hundred of shared libraries.
The JIT Cache Optimization feature addresses all the above problems through “fusing” the contents of a JIT cache into a single shared library using the main JET ahead-of-time compiler.
JIT cache optimization is a three-step process:
The samples/Classloaders/JITCacheOptimizer subdirectory of your Excelsior JET installation contains some example programs.
Alternatively, you may use the xjava launcher to run your entire application through the Caching JIT compiler and then optimize it.
Suppose you have an application that uses the JET Caching JIT compiler and you set a cache directory, for example, MyJITTemp:
export JETVMPROP=-Djet.jit.cache.dir:MyJITTemp
./MyApp
First, set the jet.jit.save.classes property to flush all dynamically compiled bytecode to disk for subsequent recompilation. Class flushing has a positive side effect: it allows you to pre-compile even those classes which do not exist on your system in the form of class files (e.g. if they are dynamically created at run time).
Note: You may use jet.jit.save.classes on the developer’s machine only and disable it in the compiled application if you find that it may cause security violation. Turn DISABLECLASSSAVING option ON in your project file to prohibit class flushing on end users’ systems.
After that, you run your application using the JET Caching JIT compiler to create a JIT cache. Along with compiled code, the dynamic compiler creates a cache descriptor in your JIT cache directory. Cache descriptor is a plain text files named
jitJET-version-code J2SE-version-code.cache
(for instance, jit37023.cache).
Create a separate project specifying the cache descriptor as a source:
------------- fuseCache.prj ------------- -lookup=*.cache=MyJITTemp +gendll -outputname=fusedCache % JETVER/VCODE hold JET/JRE version codes !module jit$(jetver)$(vcode).cache -----------------------------------------
and build it as
jc =p fuseCache.prj
As a result, you will get a new cache descriptor (e.g. jit31523.cache) created in the MyJITTemp directory and fusedCache.so created in the current directory.
Note: If the cached classes import statically compiled classes, you also need to add to the project lookup statements for the compiler to find the respective .sym and .bod files, for instance:
-lookup = *.bod = ./bod -lookup = *.sym = ./sym
At this point, you may remove all files from MyJITTemp except the cache descriptor and copy the newly created fusedCache.so to that directory. Also, remove jet.jit.save.classes property setting.
Now, if you run your executable, it will automatically use the fused cache.
You may fuse JIT Cache incrementally. For that, follow the steps described in How to optimize a JIT cache, but do not delete flushed classes and leave the jet.jit.save.classes setting intact on Step 3. If your application creates new cache entries that you wish to fuse with the shared library created at previous steps, just perform the procedure again. This saves time of dynamic compilation because the cached code is reused and compilation occurs only for newly loaded classes. To save time of static recompilation of JIT cache, you may specify =s switch to enable the incremental build mode as
jc =p =s fuseCache.prj
When you decide to stop the process, remove jet.jit.save.classes setting and run your application without flushing classes.
Note: Information in this section is applicable to Excelsior JET Professional Edition only.
If the application you want to optimize with Excelsior JET takes full advantage of Java dynamic class loading facilities, e.g. it uses custom classloaders intensively or comprises third-party libraries that do so, creating a complete and correct project file becomes challenging. To make the optimization process more straightforward, Excelsior JET provides the xjava application launcher.
xjava is a command-line tool with a syntax and semantics very similar to the conventional java/javaw launchers included with the Sun JRE. xjava loads the JET Runtime and feeds your application’s classes to the Caching JIT compiler. The resulting JIT cache can later be optimized into a single shared library by invoking xjava again with the -Xcompile option added. This will also produce a custom launcher for your application that you will be able to distribute.
The disadvantage of using xjava is that only the classes that were actually loaded during your test runs will be optimized. Moreover, when a different classloader loads a class that was previously loaded, the class may have to be compiled again. In a general case, you will have to deploy all the original jar files together with the optimized JIT cache shared library, not to mention the JET runtime shared libraries and the custom launcher. This limits the usage of this feature to situations when the distribution size is not an issue, which is typical for server-side applications.
xjava [ options ] main-class arguments
xjava [ options ] -jar jar-file arguments
For the sake of compatibility, the xjava launcher supports some standard and non-standard options of the Sun JRE launcher.
If package is specified, assertions are enabled in that package and all its subpackages. If only “...” is specified, the unnamed package is assumed. If class is specified, assertions are enabled in that class only.
If package is specified, assertions are disabled in that package and all its subpackages. If only “...” is specified, the unnamed package is assumed. If class is specified, assertions are disabled in that class only.
Note: This option is recognized but ignored, because xjava uses system classes precompiled by JET Setup. In the current version, assertions in system classes are always disabled.
Note: This option is recognized but ignored, because xjava uses system classes precompiled by JET Setup. In the current version, assertions in system classes are always disabled.
The following options are specific to xjava:
If launcher-name is not specified, the launcher will be named after the main class specified on the xjava command line (or after the jar file if the -jar option is used.)
See Runtime Selection for details.
Note: Only the options that affect code generation will be retrieved from prj-file.
The -Xcompile option changes the behavior of xjava completely. Instead of running your application, it does the following:
Any command-line options other than -Xcompile are hard-wired into the custom launcher, so the correct usage is to add -Xcompile to the options you previously specified:
xjava -cp MyLib.jar -Dfoo=bar -Xmx256M foo.bar.Main
xjava -cp MyLib.jar -Dfoo=bar -Xmx256M -Xcompile foo.bar.Main
If you want to change the default settings for JIT cache compilation, use the -Xuseprj:prj-file option, where prj-file is a regular JET project file. xjava ignores prj-file directives that do not affect code generation.
To begin using the xjava launcher, you normally just need to replace the “java” / “javaw” command with “xjava”. For instance, the following command:
xjava -cp MyLib.jar foo.bar.MainClass
will run your application with the JIT compiler caching files in the ‘jittemp” subdirectory of the current working directory. Then, you can optimize that cache by adding the -Xcompile option:
xjava -Xcompile -cp MyLib.jar foo.bar.MainClass
The result will be the custom launcher foo.bar.MainClass, optimized cache shared library, and new cache descriptor. The -cp MyLib.jar option will be hardwired into the launcher. Now you may run the optimized application by typing just
foo.bar.MainClass
To deploy the optimized application, create a new package in JetPackII and add the launcher executable to it. JetPackII will recognize the launcher and prompt you for adding the optimized cache to the package. See Adding files to the installation for details.