Note: Information in this Chapter is applicable to Excelsior JET Professional Edition only.
Excelsior JET, Professional Edition features JetPerfect Global Optimizer — a powerful facility that improves application performance and eases deployment. The use of JetPerfect gives you several important advantages:
JetPerfect is integrated with the core JET compiler. By default, JET compiles applications in the dynamic link model, i.e. compiled application classes are linked into an executable that requires one or more JET runtime DLLs containing precompiled Java 2 platform classes (see Profiles). With JetPerfect, both application classes and platform classes are linked into a single executable that does not need those runtime DLLs and does not contain redundant code and data.
Note that JetPerfect-enabled compilation takes considerably more time and requires more resources. We recommend using JetPerfect only after you ensure that your application behaves as expected when compiled with the dynamically linked runtime.
The disadvantage of JetPerfect is the inability to handle dynamic loading of arbitrary classes at run time. A prerequisite to JetPerfect usage is that all classes required by the application are known at compile time. Therefore, you may not use it in the mixed compilation model.
Note: Read the section Application domain carefully to decide whether JetPerfect is suitable for your program. If, due to any reason, you deem its usage unsafe, we recommend you to use the default, “all included” dynamic link model.
Not all Java applications are eligible for processing by JetPerfect as they have to meet the following criteria:
Therefore, applications configurable at run-time by loading some components or plug-ins are not appropriate. So are, for example, applications exploiting the RMI Distributed Class Model, in which the implementing classes are loaded from remote hosts. Nevertheless, there is still a lot of Java programs that do not use dynamic loading so intensively. Here is a few examples of successful JetPerfect usage:
JetPerfect effectively optimizes the data abstraction penalty incurred by virtual method invocation, type inclusion checks, etc. As a result, it improves the strength of optimizations performed by the JET compiler to a great extent. Analysing the entire program, including the Java 2 platform classes, allows the compiler to make more inline substitutions and allocate more objects on the stack instead of the heap.
JetPerfect performs global type analysis, makes a safe approximation of actually used classes, methods and fields and then removes redundant code and data. This technique significantly reduces the download size of optimized Java applications.
Unfortunately, JetPerfect cannot automatically accomplish code extraction for classes and methods accessed reflectively. Specifically, it concerns Java Reflection API and/or Java Native Interface (JNI) employed by many applications. This is why a substantial amount of human assistance is necessary to correctly apply global optimizations. On the whole, using JetPerfect comprises the following steps.
As compared to the default dynamic link model, two extra steps are required before creating JetPerfect-optimized executables.
For example, consider this simple program.
class Bill_G {}
class Bill_J {}
public class hello {
static public void main (String args[])
throws ClassNotFoundException
{
String subj = (args.length == 0) ?
"world" : Class.forName(args[0]).getName();
System.out.println("Hello " + subj);
}
}
Depending on the arguments that may be put on the command line (if any), both, one or none of the classes Bill_G and Bill_J must be included in the resulting executable. Walking through this example, it is easy to see that reflective dependencies cannot be automatically obtained by analysing program code.
Thus, in addition to the application’s bytecode, JetPerfect needs a complete list of classes, methods and fields that may be used reflectively during program execution. Given that certain Java 2 platform classes are accessed reflectively (from within native methods via JNI), it is almost impossible to create the list for a large application by hand.
Fortunately, JetPerfect provides facilities for the detection of reflective dependencies at run time. Moreover, you may explicitly declare the entities that must be forced into the resulting executable.
Suppose, you have a JET-compiled application that works as expected in the default dynamic link model. In order to cover its reflective dependencies, you should perform the following steps.
The JET run-time system is able to create a log including all reflective accesses to program entities occurred during execution. To enable reflection activity logging, set the jet.usage.list property.
Additionally, you set the value of the jet.default.classloader property to bootstrap or application. Without custom classloaders, a JVM loads Java 2 platform classes with the bootstrap classloader and application classes — with the application one. Nevertheless, many applications work flawlessly if the bootstarap classloader loads all classes. The choice of either classloader affects the size of the resulting executable (it will be larger if the application classloader is used). /You may determine which classloader is enough for your application classes as follows. Suppose you run your application on a JVM using the command line java -cp <directories and jars> AppMain. If you run it as java -Xbootclasspath/a:<directories and jars> AppMain and the application’s behavior does not change, the bootstrap classloader would be enough./
For conventional executables and DLLs, setting these properties may be done via the JETVMPROP environment variable at the command line or in a batch file:
SET JETVMPROP=-Djet.usage.list -Djet.default.classloader:bootstrap
MyApp.exe
This however does not work for NT services. If you need to create a log for a service,
set the properties at compile time and rebuild your application.
As a result, reflective dependencies will be automatically collected during the execution of your application. Upon termination, the dependency list will be saved in a file with the executable’s name and the ".usg" extension, created in the directory where the executable resides.
Notes:
You may re-run your application as many times as you wish, supplying, for instance, different input data. In this case, the previously created .usg file is not overwritten, but newly detected classes, methods and fields are added to it.
Warning: Be careful with updating the log and make sure to remove the previously created .usg file if the application was changed and rebuilt.
Now, stress test your application to achieve the largest possible code coverage. Ideally, you should use all features, all modes of operation, and all their combinations on large amounts of heterogenous input data.
Once the usage list has become stable (no more entries are added to .usg file), proceed to the next step.
Note: Be aware that .usg file is auto-generated so it should not be edited by hand.
This step is optional. You perform it only if certain program parts are known to be accessed reflectively during execution, or if you are not sure how they are actually used so it would be safer to force them into the resulting executable explicitly.
The precision at which you define the “must be forced” parts of your application can vary and includes the following options:
A special-case option is locale related classes providing internationalization support such as character conversion, date/time formats, etc. These classes are definitely reflected by the Java 2 platform API. For them, you may simply specify the names of the locale groups necessary for your application (e.g. Japanese, East_European, etc.).
To declare explicitly forced items, you create a plain text file with the ".fus" extension, obeying the syntax described in Section Forced usage file format.
To perform the most size effective code extraction, you specify several optimization settings and then enable global optimization. In essence, the settings control the trade-off between the size of the resulting executable and the functionality available to it. All the options described below must be explicitly set in your project file to perform global optimization.
-IncludeTimeZoneInfo+
It includes the necessary data into the resulting executable. Otherwise, switch it OFF.
-IncludeDetectedLocales+
It adds all required classes for the detected locale and all locales with the same encoding to the resulting executable /You may add support for extra locales listing them in the forced usage file on STEP 2./ .
Switching this option OFF significantly reduces the size of the executable at the expense of locale support being limited to the current locale only. Also, only those locale attributes used when profiling reflective dependencies will be included into the resulting executable.
-IncludeLoggingAPI+
Otherwise, switch it OFF to reduce the size of the resulting executable.
!module MyApp.usg
!module MyApp.fus
-main=MyAppMainClass
You have to specify the main class explicitly, because JetPerfect compiles not only your application classes, but also JDK classes, some of which contain method
static public void main (String[])
so the compiler is unable to determine the entry point automatically.
Recompile the entire application with the PERFECT compiler option enabled.
jc =p MyApp +perfect
Note that compilation will take much longer than in the default mode, because not only application classes but also Java 2 platform classes will be optimized.
When QA testing the optimized executable, you may encounter the problem that not all reflective dependencies were detected with run-time profiling on the build machine. For instance, the JetPerfect optimization was performed on an NT-based system (Windows NT, 2000, XP) but after porting the executable to a Windows 9x flavour (95, 98, ME) some extra classes are proved to be reflected.
In such case, the executable provides feedback: the message
JETPerfect: Incomplete .usg file. Extra usage list generated.
is printed to console /If your application was compiled with suppressing the console window, a pop-up window with this message shall appear./ (to the event log for NT services), an extra .usg file is created in the directory where the executable resides, and the application terminates with non-zero return code.
If the optimized executable works improperly but no exta usage list is created, you may try to enable extra checks for detection of missed reflective dependencies by setting the following property:
SET JETVMPROP=-Djet.report.not.found.entities
MyApp.exe
It results in creation of an extra .usg file listing the names of the classes that might be completly removed by the optimizer.
If such a problem occurs, you have two options to remedy it.
This way, you just rename this newly created .usg file, say, to MyApp_extra.usg, move it to the build machine and add this line to the project file:
!module MyApp_extra.usg
At this point, you must also choose the aggressiveness with which JetPerfect has to complete the list of reflective dependencies. Options are adding exactly those entries listed in the extra .usg file or including all listed classes (with their members) in whole. If you prefer the exact completion of the reflective dependencies, add this equation to the project file
-USGFILEAUTOCOMPLETION=OPTIMIZED
otherwise, add
-USGFILEAUTOCOMPLETION=SAFE
for more extensive completion.
Finally, recompile the entire application on the build machine
jc =p MyApp +perfect
and proceed with your QA testing.
The process of creating extra .usg files is inherently repetitive. Of course, after rebuilding the optimized executable, execution goes further but yet another lack of collected dependencies may be determined. As recompilation with enabling JetPerfect takes a while, in some cases, it would be easier to complete the list using the original executable built without JetPerfect.
To do that, you employ JetPackII to create CD image which includes the original executable and the .usg file created on the build machine. As a result, you have a self-contained directory that you simply copy to the system where the problem occurs. Then you run the original executable and perform the scenario caused the problem in the JetPerfect-optimized executable thus completing the original .usg file. Finally, you move the file to the build machine and recompile the entire application as
jc =p MyApp +perfect
This technique substantially reduces the number of iterations necessary to create the complete list of reflective dependencies.
JetPerfect compiles your application into a single executable that includes the code of both application and platform classes. If your application does not employ AWT/Swing, the resulting executable contains all the necessary native methods for Java 2 platform classes. In conjunction with JET’s resource binding capabilities these features enable you to deploy JetPerfect-optimized pure Java programs simply by copying the executables to other systems.
However, JET is not capable of linking in arbitrary native methods, as they typically reside in DLLs. Therefore, globally optimized AWT/Swing-based applications and applications employing third-party libraries with native methods require some DLLs to run. But that does not hinder easy deployment. The JetPackII utility distributed along with JET allows you to create a single self-extracting executable including the compiled application, DLLs with native methods, and any resources required by application (image files, fonts, etc.), if they are not bound to the executable (see Resource binding).
Yet another option you may find in JetPackII is creating a CD image, which in particular enables you to deploy your application simply by copying a self-contained directory to other systems.
For security reasons, you may wish to disable generation of the reflective dependencies list in the end-user version of your application. If so, toggle the option DISABLEUSAGELIST ON in the project file and re-build the executable.
If you have project and .usg files created with JET versions prior to 3.5, you may continue using them. However, when switching to JET 3.5 (or later versions), you have to add the new global optimization settings to your project files.
Moreover, starting from v3.5 the JET run-time system performs stricter checks of the completeness of the reflective dependencies used for JetPerfect optimization. To disable the checks, you may set the jet.disable.jetperfect.checks property. However, it would be more reliable to perform completion of the list of reflective dependencies using the feedback mode as described in Section Troubleshooting.
The following limitations are to be removed in furture versions of JetPerfect.
A forced usage (.fus) file is a plain text file each non-blank lines of which contains either a section header or section items following the header.
At will, you may force classes (in whole), separate class members, packages, jars, and classes for particular locale groups into the resulting executable.
The respective sections have the following syntax:
# this is a comment
#---------------------------------------------------------------
[Methods]
{<qualifiled method name>[<signature>]}
# e.g.
# java/lang/String.equals
# java/lang/String.<init>(V)V
#---------------------------------------------------------------
[Fields]
{<qualifiled field name>[@<signature>]}
# e.g.
# com/company/Goo.f
# com/company/Foo.g@[java/lang/Cloneable;
#---------------------------------------------------------------
[Classes]
{<qualified class name>}
# e.g.
# java/io/Win32FileSystem
#
#---------------------------------------------------------------
[Packages]
{<package name>}
# e.g.
# sun/java2d/loops
# com/company/stuff
#---------------------------------------------------------------
[Jars]
{<jar name>}
# e.g.
# jh.jar
# activation.jar
#---------------------------------------------------------------
[Locales]
{<locale group name>}
# e.g.
# Korean
# East_European
The [Classes] section includes class names prepended by their package names (the package delimiter is "/").
Specifying classes, you force all their members into the resulting executable.
The [Methods] section includes method names prepended by their class names (the method name delimiter is "."). The string "<init>" used to represent constructor names.
If a method is overloaded (i.e. there are several methods with the same name and different signatures), its signature should be appended to the method name. The format of the signature is the standard JNI method signature described in the following table:
| signature | Java type |
| Z | boolean |
| B | byte |
| C | char |
| S | short |
| I | int |
| J | long |
| F | float |
| D | double |
| V | void |
| Lfully-qualified-class; | fully-qualified-class |
| [type | type[] |
For example, the method
int foo (int n, Object o, int[] arr);
has signature
(ILjava/lang/Object;[I)I
The [Fields] section specifies fields names prepended by their class names (the field name delimiter is ".").
The [Package] section lists package names (the subpackage name delimiter is "/").
Specifying a package, you force all classes from the package and its subpackages, recursively. Note that the scope affected by this section is your application’s packages as well as the Java 2 platform packages. For instance, if you simply specify com package, all packages prefixed with com. will be forced including those from the Java platform API. It is recommended to force packages with a higher precision including subpackage names, e.g. com/company/package.
The [Jars] section contains bare jar file names. Note that you have to add full paths to the jars to the project file via LOOKUP statements. For example,
-lookup = *.jar = C:\App\DirectoryWithJars
Including a jar file, you force all packages from the jar into the resulting executable.
The [Locales] section may include the following locale group names depending on the JRE version in use:
East_European Cyrillic Latin_1 Greek Turkish Hebrew Arabic Baltic Thai Chinese Japanese Korean
English
East_European Cyrillic Latin_1 Greek Turkish Baltic
The locale groups are organized following this principle: all locale-related classes with the same encoding (code page) are put into one group. Specifying a locale group, you enable all required functionality such as character conversion, date/time format, currency format, etc.
Thus, typical content of a .fus file looks as follows:
[Methods]
java/lang/System.initializeSystemClass
java/lang/ref/Reference$ReferenceHandler.run
[Classes]
java/io/Win32FileSystem
java/lang/Win32Processes
[Packages]
sun/java2d/loops
com/company/stuff
[Jars]
jh.jar
activation.jar
[Locales]
East_European
Cyrillic
Latin_1