In a traditional developer toolkit that includes a static compiler and linker, e.g. a C++ toolkit, static libraries are linked together with the application code and all references between them are resolved at link time. Contemporary operating systems provide support for dynamic link (or shared) libraries. Unlike static libraries, they are linked with executables at run time. If an executable contains direct references to methods and/or fields from a shared library, linking is performed by the dynamic linker when the executable starts. This is called load time linking. Some applications, however, postpone the linking process to a later phase so that the application’s code loads shared libraries and resolves references using the system API. This is called run time linking. The executable and a set of loaded shared libraries form a so called working set of an operating system process.
Typically, working sets of different processes have common components, such as shared libraries implementing the system API functions. These components are shared between processes, that is, only one copy of code and constant data from a shared library is loaded into memory. This implies the following important benefits for native applications employing the dynamic link model:
The question is whether you the Java developer can benefit from dynamic linking supported by the target operating systems? If you are reading this manual, the answer is YES because you most probably have a copy of Excelsior JET JVM installed on your system.
First of all, the Java 2 platform classes are pre-compiled into a set of shared libraries when your create a JET profile. Therefore, they will be shared between the processes established by JET-compiled executables, just like the shared libraries providing the OS API are shared between conventional native applications. This effectively means that you already have a JVM capable of sharing constant data (such as reflection information) and code (e.g. methods of Java 2 platform classes) between several running Java applications.
As for your application’s code and third-party Java APIs not included in the Java 2 platform, you have two possibilities of using dynamic libraries:
The next Sections describe both techniques in details.
To create a shared library callable from applications written in another language, you can use the JET Control Panel: click Invocation DLL on the Welcome Screen (see Starting Excelsior JET Control Panel) and select the desired jars on the Start page (see Step 1: Describing an application).
Using Invocation shared libraries is a bit tricky. Like any other JVM, Excelsior JET executes Java code in a special isolated context to correctly support exception handling, garbage collection, and so on. That is why Java methods cannot be directly invoked from a foreign environment. Instead, you have to use the standard Java 2 platform APIs, specifically the Invocation API and Java Native Interface (JNI). See samples/Invocation in your JET installation directory for detailed examples.
Note: Before you begin, learn about multi-app executables (see Multi-app executables) to decide if you really need to create a multi-component application. For example, if you plan to compile two executables sharing common libraries and both executables are always deployed together, it would be much simpler to build a single multi-app executable rather than two executables and shared library.
Currently, Excelsior JET Control Panel does not support creating multi-component applications so you have to use the command line interface to the JET compiler. However, the Control Panel can assist you at the first steps of creating a multi-component application. Please, read this entire section carefully so as to correctly setup project files for shared libraries/executables and avoid problems with compilation and running multi-component applications.
This section contains step-by-step instructions for creating multi-component applications. Please, note that all the steps described below are obligatory.
It is supposed that you have a set of jar files containing all class files and resources necessary to your application. If your application includes some standalone classes/resources residing in a directory, you have to create a jar file from this directory’s contents. This is important because the JET compiler can automatically pack all resources from jar files to the resulting shared libraries and executables. Moreover, it is much easier to write a JET project file if all classes and resources are packed to jars.
First of all, create a project to compile all the jars into a single executable using the JET Control Panel, then proceed to the Step 3.
If there are consistency warnings, click the Class View buttons and inspect the Warnings tab below. If it says there are duplicated classes, fix the project by removing duplicated classes from your jar files, and repeat the project creation process until there are no more class duplications in the compilation set.
If warnings about unresolved import dependencies are displayed, you can ignore them at this step, provided the application works under the HotSpot JVM flawlessly.
Finally, go through the remaining pages to set up the project, compile it and make sure that your application works properly.
Note: The created project file cannot be used for subsequent compilation of shared libraries and executables.
On this step, you define the contents of shared libraries and executable(s) being created. Specifically, you break down the compilation set, checked for consistency as decried above, into several compilation sets, each to be compiled into a separate component (executable or shared library). The breakdown is not arbitrary as it should obey the following rules:
So be ready that the model "one jar — one shared library" cannot be used if the jars have cyclic interdependencies.
The JET Control Panel can assist you in defining multiple compilation sets as follows:
The compilation set CSexe, including the jar with the main class of your application has to be defined last as it will be compiled to the executable. The other compilation sets will be compiled to shared libraries.
Actually, you can define several compilation sets that reference CS1,...,CSk to create several executables with common shared libraries.
Let us consider a typical example of multiple compilation sets. Suppose you have two applications using a Java Optional Package, such as JavaHelp. You may define three compilation sets: the first one contains the jars of JavaHelp and the others — jar files of your applications. This breakdown implies that you can create a shared library for the JavaHelp API and two executables using that shared library.
Create an empty directory called, for instance, MultiCompBuild. In the directory, create JET projects (plain text files with the extension .prj) - one project per each component. For all but the last compilation sets defined on STEP 2, create projects named, say, CS_1.prj,..,CS_k.prj after the following template:
-OUTPUTNAME=shared-library-name (without extension)
%jar files from this compilation set
You have to edit only the -OUTPUTNAME=, -LOOKUP=*.jar= , !CLASSPATHENTRY and !uses settings. Note that you have to add !uses directives (see The !uses directive) only to the projects that depend on other projects.
For the compilation set corresponding to the main executable, create a project file called, for instance, CS_EXE.prj, after the following template:
-OUTPUTNAME=executable-name (without extension)
. . .
%jar files from this compilation set
. . .
In this template, you have to edit the -OUTPUTNAME=, -LOOKUP=*.jar=, -MAIN= and !CLASSPATHENTRY settings.
Set MultiCompBuild as the current directory and compile these projects using the commands
jc =p CS_1.prj ... jc =p CS_k.prj jc =p CS_EXE.prj
Now you can run the resulting executable.
Note that this particular order of compilation is important to transparently resolve references between the components being compiled. This is exactly the order in which the compilation sets were defined on STEP 2.
If you change the jars from CSexe compilation set, it is enough to recompile only the executable with the command
jc =p CS_EXE.prj
However, if jars compiled into one of the shared libraries were changed, it is necessary to recompile that shared library as well as the components that were built after it, including the executable. Finally, if modifications of shared library’s jars change the import relationships (e.g. new classes are referenced), you may have to repeat the whole process starting from STEP 1. Fortunately, that is a rare case if you create shared libraries from third-party jars which do not change.
As it was said above, shared libraries can be mapped to the process via load time and run time linking. This Section describes how JET-compiled applications employ both ways of linking.
If one component directly uses classes from another component, this always implies load time linking /!uses directive (see The !uses directive) also forces load-time linking for the imported component./ . Examples of direct uses are creating a class instance via the new operator, extending a class from another component, accessing static members, etc. However, if one component employs only the Reflection API to use classes from another components, load time linking does not occur because there are no direct references between the components. A typical example is loading classes via the Class.forName() method and passing the obtained class objects to the methods from the java.lang.reflect package.
Note that Class.forName() and the Reflection API also work for all classes from the components linked to the process at load time.
JET-compiled applications can load shared libraries on demand in a portable way, that is, without using the operating system API. The Class.forName() method serves for this purpose so that looking for a class from a pre-compiled but not yet loaded shared library results in loading that shared library at run time.
Suppose component B has to load component A at run time. To enable run-time linking, two pre-conditions should be met:
The mapping allows the Class.forName() method to succeed for classes precompiled into shared libraries loaded at run time. You need to tell the JET runtime in which shared library to look for each class or package. You may do that by adding the following options to the JETVMPROP equation or the JETVMPROP environment variable:
is a fully qualified name of a class, such as com.MyCompany.MyClass
is a class name prefix followed by the “*” character:
This syntax is useful when you compile an entire package into a shared library.
is the name of the shared library containing the class class-name or classes whose fully qualified names begin with class-prefix. If dll-name contains spaces, enclose it in double quotes:
Note that if you do not specify a pathname, the shared library will be sought along the PATH , but that may slow down application execution. If you specify an incomplete pathname, such as “My App/MySO.so”, that pathname will be appended to the name of the current directory and then to the name of each directory along the PATH.
You can specify more than one -dll option. If the name of the class being loaded matches more than one class-prefix, the class will be sought sequentially until it is found or there are no more matching prefixes.
% MYAPP.PRJ Shell script: #RUNMYAPP.SH
Shell script: #RUNMYAPP.SH
If some classes cannot be pre-compiled, for example, those of third-party plug-ins to your application, the JET Runtime enables Mixed Compilation Model.
In this case, the bytecodes for the classes are sought in accordance with the conventional search algorithm used by the Java 2 platform. Then, the bytecodes are loaded and compiled at run time.