This document describes how the developer can control the execution of the compilation system of the Sun Java Real Time System (Java RTS) 2.2.
Specifying the Methods for Initialization Time CompilationCompilation Modes for the Thread Types
In a Java HotSpot virtual machine, the compilation of bytecode into native code is normally transparent to the developer. The primary performance criterion is the average execution time of applications, which translates to throughput for server-like systems. Certain VM operations can be allowed to execute in an unpredictable amount of time if they do not occur often and if this allows the most common cases to execute faster. The gain on the average execution time is positive with such a policy. For example, in the case of compilation, since compiled code executes faster than interpreted code, the overall program can run faster if frequently executed pieces of code are compiled during program execution, even if this means suspending the application during the compilation.
However, in a real-time environment, execution must be deterministic, that is, response times should be guaranteed and predictable. Variations in response time are generally of more concern than execution speed.
Determinism can be expressed in terms of jitter, where jitter measures the variation of response time or execution time. One of the most common sources of jitter is run-time compilation. If compilation interrupts the execution of a section of time-critical code, determinism is seriously compromised.
Another source of unpredictable compilation-related jitter is the on-demand resolution of symbols (fields, classes and methods). When an unresolved symbol is encountered during the execution of the code, the VM uses some extra, non-deterministic, code to resolve the symbol. In this way the VM avoids resolving symbols that are never actually used. However, since the timing of the resolution operation itself is unpredictable, this is unacceptable in time-critical code.
This document will explain how to control these two sources of jitter.
Java RTS currently supports two compilation modes: Just-In-Time Compilation (JIT) and Initialization Time Compilation (ITC).
Note: (Solaris OS) In order to execute the 64-bit Java RTS VM,
As explained in the previous section, the JIT compilation scheme, which is the default for a Java program, can break the determinism of a real-time application. Therefore, you need to configure your real-time application to use the ITC scheme. This scheme is very powerful and very flexible, which means that it is also fairly complicated to understand and use to its fullest advantage. The ITC scheme is described in detail in the rest of this document.
However, for purposes of getting off to a quick start with Java RTS, you can use the following minimum configuration (with the default behavior) that is recommended for assuring determinism. You will execute your program with a command that will automatically build two files:
The precompile and preinit files are cumulative, that is, each time you run the application, more methods and classes can be added to the files. Therefore, you should run the application as many times as necessary, for example, using different input parameters or data, to ensure that all the time-critical real-time code has been executed, which ensures that the precompile and preinit lists are complete.
You can use the following command as an example of a minimum level of ITC configuration:
java \ -XX:+RTSJBuildCompilationList \ -XX:+RTSJBuildClassInitializationList \ -XX:CompilationList=<your-precompile-file> \ -XX:PreInitList=<your-preinit-file> \ <your-application> <arguments>
Here is the explanation of what is happening with the example command above:
Note that you can use the
java \ -XX:+UseITC \ <your-application> <arguments>
See a full example in Complete Example of ITC Usage for a sample program, including code and commands.
See also the Java RTS Quick Start Guide
Just in Time (JIT) compilation is the usual, transparent, compilation mode of a Java virtual machine. JIT compilation selects the methods to be compiled based on the frequency of their execution.
The section Compilation Modes for the Thread Types explains how to enable and disable the JIT compilation mode for the different thread types.
The basic principle of JIT compilation in Java RTS is the same as in the Java HotSpot VM. Compilation of a method is triggered in two situations:
The time at which the compilation occurs is totally under the control of the virtual machine and cannot be predicted in 100% of the cases. For example, if a section of critical code is only executed in rare conditions, the invocation counter of a method in that code may reach the threshold after a very long running time. However, there is no way to know when the compilation will occur, and this uncertainty is not acceptable in a real-time application.
Note that, if most of the methods of an application are interpreted, the CPU load can be so heavy that compilation is delayed forever. In that case, you should compile at least a few methods.
Compilation that is triggered by an overflow of the invocation counter can be asynchronous or synchronous. Asynchronous compilation is enabled by default in order to improve determinism. It can be disabled on the command line with the flag -XX:-BackgroundCompilation.
In asynchronous (or background) compilation, the compilation of the called method is initiated, but the thread that initiated the compilation is not blocked waiting for the compilation to complete; the thread continues executing the method in interpreted mode. Compilation continues asynchronously, in the background. After the compilation is complete, subsequent invocations of that method will execute the compiled version. However, remember that while methods are being interpreted, the CPU load can be so heavy that compilation is delayed forever.
In synchronous compilation, the thread that initiated the compilation is blocked until the method is compiled. After the compilation is complete, the thread executes the compiled method. This improves throughput earlier in the execution, but the application pauses during the compilation, and this can impact determinism.
A Java program is not usually deterministic during its entire execution, that is, only certain parts of it are deemed time-critical. It is convenient to refer to a real-time application as running in different phases:
During the initialization and reconfiguration phases, the application does not need to execute deterministic code, and these are the phases where operations that potentially have an unpredictable execution time should be executed.
With the Initialization Time Compilation (ITC) mode, specified methods of a program are compiled during the initialization of their classes, that is, in advance of the time they would be compiled in JIT mode. In this way you can ensure that compilations of critical code occur during the initialization phase.
The section Compilation Modes for the Thread Types explains how to enable and disable the ITC compilation mode for the different thread types.
Class initialization is non-deterministic, because it is executed at the first use of the class. Hence class initialization should be executed during the initialization phase of the application. Therefore, class initialization is a natural place to spend CPU time compiling critical code.
As a result, if you ensure that all the classes that will be used during the time-critical phase are initialized before entering this time-critical phase, then this also ensures that all the requested compilations are performed before entering the time-critical phase. This contributes to the determinism of the application.A class is said to be used during the time-critical phase either if it is referenced from some bytecode executed during the time-critical phase or if it contains a method that is executed in the time-critical phase.
You can control three aspects of ITC:
These specifications can be performed either on the command line at VM start-up or programatically during runtime with the Compiler class in the extended RTSJ package. The ability to specify these methods and classes during execution is especially useful in user-defined class-loader-based architectures, where some methods might be written after the application has started running.
With ITC, you provide the descriptors of the methods to be precompiled in a list that is used to specify the methods that must be compiled when the class is initialized. This is the compilation (or precompile) list.
This list contains a method description on each line in the following format:
<class name> <method name> <method signature> <thread type>
The <class name> is the fully qualified name of the class that defines the selected method, the <method name> is the name of the method, and the <method signature> is the signature formatted as specified in the Java Virtual Machine Specification.
It is possible to use wild cards in the <class name>, the <method name>, and the <method signature>. See the next subsection Using Wild Cards in the Compilation List for a complete description, with examples.
The <thread type> can have one of the following values:
Note that the empty string supports earlier versions of the compilation list, although a warning message is generated. If no thread type is specified, then the system triggers the precompilation of the specified method for both RTT and NHRT.
The <thread type> variable specifies the types of threads for which
the methods will be precompiled. For example, if the <thread type> variable
Important: If a particular method (that is, the same class, method, and signature)
matches more than one line in the compilation list, then the method will be precompiled
for all the thread types specified in all the matching lines. For example,
if a method matches a line with the <thread type> equal to
Here is a simple example of a compilation list:
java/io/PrintStream println ()V rtt java/io/PrintStream println (Ljava/lang/String;)V rtt java/io/PrintStream write ([BII)V rtt
More examples are shown in the subsection below Examples of Compilation List.
You can specify the list in two ways:
Using Wild Cards in the Compilation List
The full grammar for the syntax of the compilation list is the following:
<line> ::= <class name> <method name> <signature> <thread tuple> <class name> ::= <name> | <name>'*' <method name>::= <method name VM> | '*' <signature> ::= <signature name VM> | '*' <thread type>::= 'nhrt' | 'rtt' | 'jlt' | 'allrt' | 'rttjlt' | 'nhrtjlt' | 'all' | <empty string> <name> ::= 'a name that is acceptable for a class or method as specified in the VM specification' <signature> ::= 'a signature as specified in the VM specification'
The <class name> is a fully qualified class name, with the forward slash character ("/") as separator.
The asterisk (*) is the wild card. As you can see from the grammar, you can use the wild card after the beginning of the class name, in place of the method name, and in place of the method signature. See the next subsection for some examples of using wild cards.
Examples of Compilation List
The following are some examples of the compilation list, including the use of wild cards.
For a full example of generating and using the compilation list, first see Generation of the Lists for a description of the automatic generation of the list, and then see Complete Example of ITC Usage for a sample program, including code and commands.
To guarantee determinism, you must preinitialize the classes that will be later used in the time-critical phase. To do so programmatically, you can use the following call to load and initialize a specified class:
java.lang.Class.forName(<class name>,true,<class loader>);
You can choose to make as many calls to this method as you have time-critical classes.
This scheme has a drawback: the initialization code must be changed each time the time-critical code is modified in a way that adds a new class.
To facilitate your work, you can ask Java RTS to initialize classes at a specified time. This is done by providing a file that contains a list of the names of classes that must be preinitialized.
You can specify the list in two ways:
Warning: In some rare cases, the order in which static initializers are executed may be of some importance, and executing them in an arbitrary order may generate problems. Even though this is considered bad programming practice, some legacy code may cause problems of this kind.
For a full example of generating and using the preinit list, first see Generation of the Lists for a description of the automatic generation of the list, and then see Complete Example of ITC Usage for a sample program, including code and commands.
Java RTS can help you reduce your work and eliminate oversights in creating the lists of methods and classes that might be needed at run-time. You can make a preliminary run of your program and request Java RTS to generate the compilation list and preinit list for you. Then you can use these lists as input to final run of your program.
All these files are stored in the directory that was the current directory when you launched the VM.
Generation of the Compilation (Precompile) List
When you run Java RTS with the option -XX:+RTSJBuildCompilationList, the VM generates a file containing a list of all the methods that were interpreted or compiled by a thread whose compilation policy is ITC. (The default name for this output file is nhrt.precompile, but you can specify a different name with the
Generation of the Preinit List
When you run Java RTS with the option -XX:+RTSJBuildClassInitializationList, the VM generates a file containing a list of all the classes that were referenced during the compilation of methods at initialization, in the order in which they were initialized. The order might be important, depending on the way the static initializers were written. (The default name for the output file is itc.preinit, but you can specify a different name with the
You can also combine both the creation of the lists and the use of the lists on the same command line. Let's take the compilation file as an example. If the file does not exist or is empty, Java RTS will generate it during the run; you might experience some jitter in this case, because the frequently-executed methods were not precompiled. But during the next run, Java RTS will use the file that was generated in the previous run in order to precompile the classes containing those methods. By specifying the file generation and file use in the same command, you reduce the risk of forgetting a step. Moreover, the generated file is cumulative: if additional methods are executed in the second run of an application, these methods are added to the list of methods that were written to the file during the first run.
Below is an example of combining the creation of a list and the use of the list in one command:
java <vm-options> -XX:+RTSJBuildCompilationList \ -XX:CompilationList=<your-file-name> <your-program>
You can safely leave these options on the command line for each execution: they have an effect only at the shutdown of the VM and therefore have no impact on the performance of your application.
Warning: In order for the generated lists to be complete, you
must make a set of runs that cover all of the
time-critical code. Statement coverage might not be enough in cases
where the application makes use of the polymorphism of method calls,
which is encouraged by object-oriented design. Branch or more general path
coverage might be necessary, depending on the complexity of the design.
In addition, the
For a full example of generating and using the compilation list and preinit list, see Complete Example of ITC Usage for the sample program, including code and commands.
The ITC system manages two lists (precompile and preinit), which are written to two different files. These files have default names, although you may specify your own name.
When you specify a file name on the command line, you must specify the absolute path to the file. Be sure to provide write access to the files and the corresponding directory
These files are cumulative, that is, during each execution of the program, information is added to the files. For example, if you execute your program with a precompile file already populated with methods, and you request the automatic generation of the precompilation list, then the names of any new methods that are executed are added to the file, and the previous method names are retained. No data is lost.
At VM startup, any existing precompile or preinit file is
copied to a backup file, in the same directory, with the extension
As described in the section Generation of the Lists, you can request Java RTS to automatically generate any or all of the lists for you. In this case, you must execute your program at least three times before determinism can be guaranteed.
Let's take the example of generating the precompile and preinit files with the following command:
java <vm-options> -XX:+RTSJBuildCompilationList \ -XX:+RTSJBuildClassInitializationList \ -XX:CompilationList=my-precompile-file \ -XX:PreInitList=my-preinit-file \ my-program
The first time you run the program, Java RTS
updates the existing precompile list (if the specified
During this first run Java RTS also adds entries in the preinit file for classes containing the methods in the precompile
file, but the preinit file is probably not complete at this time. (It adds entries to the preinit list
if the specified
During the second run, Java RTS compiles the methods in the precompile list as their classes are initialized. It also updates the existing preinit list with the names of classes that are referenced in the compiled code.
At the beginning of the third run, when the VM starts up, Java RTS preinitializes the classes in the preinit list and precompiles the methods in the precompile list. Now, when the application executes, it should be more deterministic. Be sure to run the application as many times as necessary to ensure code coverage, that is, to make sure that all code branches have been executed. All the classes referenced by the real-time threads should be preinitialized, and all the methods in those classes that are invoked by real-time threads should be precompiled when their classes are preinitialized. Therefore, there should be no jitter due to compilation or symbol resolution in the real-time code during program execution.
In addition, you should run your program as many times as necessary to guarantee that all the real-time code has been executed so that all the classes and methods that are referenced by the real-time threads are included in the lists. For example, you might want to run the program with different input variables or data. Each time that you change a variable, be sure to run the program at least three times so that the precompile and preinit lists include any new classes or methods that were referenced in the newly executed real-time code.
In this example, we are assuming the default behavior, that is, that ITC is enabled for RTTs and NHRTs, but not for JLTs.
See also the full example in Complete Example of ITC Usage for a sample program, including code and commands.
One way to determine if you need to add more methods to the precompile
list is to add to the command line the option
Note: This command only reports methods that were actually compiled; there can be additional methods that should be ITC-compiled, but were not compiled at all during that run and are therefore not detected with this option.
The output includes an identifying compilation number, the full method name (but not the signature), the total size of the bytecode, and a time stamp. After the compilation number, and before the method name, five character positions contain additional information about the compilation, where the symbols have the following meanings:
The following is some example output from the
103 b N Deterministic::computeFibs (46 bytes) date(200947316498777) 104 b R Deterministic::computeFibs (46 bytes) date(200947318378861) 105 !b N Deterministic$RealTimeFibonacciLoops::run (164 bytes) date(200947367899360) 106 !b R Deterministic$RealTimeFibonacciLoops::run (164 bytes) date(200947372600193) 107 b N javax.realtime.HighResolutionTime::
This output shows, for example, that compilation number 106 had exception handlers, was blocking, and was for a RealtimeThread. Compilation number 113 was wrapping a native method call, was blocking, and was for a NoHeapRealtimeThread.
Note that each type of thread has its own copy of the compiled code. For example, RealtimeThreads execute only code compiled for RealtimeThreads, not code compiled for other types of threads. In the example output above you can see that the same method was compiled once for NoHeapRealtimeThreads and again for RealtimeThreads.
The different types of threads in a real-time application are instances of
Determinism is important for instances of NoHeapRealtimeThread (NHRT), and therefore ITC is enabled by default. This means that NHRT-type methods in the compilation list are precompiled at initialization time; other NHRT-type methods are interpreted.
ITC can be disabled for NHRTs with the
JIT compilation is not allowed for NHRT instances.
For instances of RealtimeThread (RTT), the situation is a bit more complex than for NoHeapRealtimeThreads.
Determinism is also important for RTTs, and therefore ITC is enabled by default.
ITC can be disabled for RTTs with the
However, RTTs must balance throughput and determinism. Therefore,
JIT compilation is also enabled by default.
JIT can be disabled for RTTs with the
When ITC is enabled for RTTs, the methods in the compilation list are compiled at
class initialization. All other methods are initially interpreted,
and then they are either JIT compiled
if JIT is enabled for RTTs (
When ITC is disabled for RTTs, RTT instances execute code either JIT compiled
if JIT is enabled for RTTs (
With the RTGC, we recommend asynchronous JIT compilation, and this is the default mode for RTTs. This compilation mode will produce code that runs faster at a later time, after the compilation finishes, but there is no spike in the execution time (see graph). This mode usually does not endanger determinism if interpreted code in this application was fast enough for the particular deadline.
With the Serial GC, users may want to turn on synchronous JIT compilation. (This is accomplished by disabling the default asynchronous JIT compilation with the -XX:-BackgroundCompilation command-line option.) However, this mode of compilation is not recommended with the RTGC because the thread that triggers the compilation is blocked during compilation. This causes a spike in the execution time (see the graph), and this breaks the determinism requirement. In fact this can create unbounded jitter for all the threads, which is unacceptable in a real-time system.
For instances of the java.lang.Thread (JLT) class, throughput and
start-up time are more important than determinism. As a consequence,
by default ITC is disabled and JIT compilation is enabled.
Note that the only way to disable JIT compilation for JLTs
is to run the application in interpreted mode (
Unlike with the Java HotSpot VM, with Java RTS the JIT compilation is asynchronous by default. Synchronous compilation is dangerous because a JLT blocked on compilation while holding a critical lock could delay an RTT. If you do not use shared locks, or if you use the Serial GC, you can enable synchronous JIT with the flag -XX:-BackgroundCompilation.
ITC can be enabled for JLTs with the flag -XX:+ITCJLT. Using ITC for JLTs can help performance. If you know in advance the behavior of an application with respect to JIT compilation, then you can further speed up the application by specifying the methods to be compiled at class initialization (ITC). This reduces the time spent executing interpreted code. For applications that have a short run time, this almost always provides a gain in performance.
When ITC is enabled for JLTs (-XX:+ITCJLT), the methods in the compilation list are compiled at class initialization, and all other methods are JIT compiled.
When ITC is disabled for JLTs (-XX:-ITCJLT), all JLTs execute code compiled in JIT mode.
This section shows graphically the effect of the compilation mode on execution time, as a method is invoked a number of times. In each graph, the x-axis represents the number of times the method is invoked, and the y-axis represents execution time. As you can see on the y-axis, precompiled code takes the least amount of execution time, interpreted mode takes more, and compilation itself takes even more execution time.
In the figure below, a method is compiled at initialization time (ITC).
Figure 1 Execution of Precompiled Code
Regardless of the number of times the method is invoked, the execution time remains the same. Therefore the jitter is theoretically zero (in practice, very small). The method is not interpreted, and no compilation takes place during execution.
The next figure shows what happens with asynchronous JIT compilation.
Figure 2 Execution of Code With Asynchronous JIT Compilation
In the beginning of the run, the method is executed in interpreted mode until JIT compilation is triggered by one of the internal counters. The method that triggered the compilation continues executing the method in interpreted mode, while the compilation runs in the background (asynchronously). When the compilation finishes, subsequent invocations of the method will execute the compiled code, and execution time will be lower.
In this case, the maximum jitter is the difference between the execution of the interpreted code in the beginning and the later compiled code. But this jitter is known and predictable.
In the figure below, synchronous JIT compilation has been specified (by disabling the default asynchronous JIT compilation with the -XX:-BackgroundCompilation command-line option).
Figure 3 Execution of Code With Synchronous JIT Compilation
In the beginning of the run, the method is executed in interpreted mode until JIT compilation is triggered by one of the internal counters. This compilation causes a spike in the graph for the execution time, because the thread that triggered the compilation is blocked while the compilation runs (synchronously) to completion.
The maximum jitter is the difference between the CPU time to perform the compilation itself and the CPU time to execute the compiled code. This jitter is unpredictable in two ways: we cannot know when the compilation will occur, nor how much time it will take, because other compilation requests might be enqueued. Since this could potentially seriously disrupt a critical phase of the application, this situation is unacceptable in a real-time system.
With this option, the VM also logs two other events: entry into and exit out of steady mode. The VM is in steady mode when it is in a time-critical execution phase, that is, a phase where it should not execute a non-deterministic operation. Entry into and exit out of steady mode can be marked by making calls to methods in the SteadyMode class in the extended RTSJ package. Having such events logged can help you determine whether or not an event that causes jitter occurred in a time-critical phase.
See the Command-Line Options section of
the Java RTS Tools, Tuning, and Troubleshooting Guide for a description of the log file
produced by the
These parameters are also listed in the Java RTS Options document.
The table below summarizes the command-line parameters that are used to control the compilation options for the various thread types. These parameters are intended either for a production environment or a development environment, as indicated in the "Env" column.
These parameters are also listed in the Java RTS Options document.
The Java RTS virtual machine can be configured to run with or without
the Real-Time Garbage Collector (RTGC). If the RTGC is not used, then the default HotSpot serial collector is used.
(The RTGC is enabled by default but can be disabled with the command-line parameter
Java RTS uses two optimizations for virtual/interface calls to non-final methods:
These optimizations might impact the balance between throughput and determinism
and might cause extra jitter in the steady state of the application.
Virtual method call inlining (also known as Class Hierarchy Analysis, or CHA) is enabled by default for java.lang.Threads (JLTs) and RealtimeThreads (RTTs)
and can be fully disabled with the option
Inline cache calls are enabled by default for JLTs and RTTs and can be disabled as follows:
Note that these optimizations do not concern NoHeapRealtimeThreads (NHRTs).
The following table lists the command-line options that Java RTS provides to control the use of these optimizations, as described above. All these parameters are intended for a production environment.
The following table summarizes the use of these parameters, where "Y" means that the parameter can be used for a particular combination of thread type and GC; otherwise, the parameter is not applicable for that combination.
This section contains a complete example of using ITC with a sample program.
ITC - Initialization Time Compilation
Java RTS - Sun Java Real-Time System
JIT - Just In Time compilation
JLT - an instance of the
NHRT - No Heap Realtime Thread; an instance of the
RTGC - Real-Time Garbage Collector
RTT - Realtime Thread; an instance of the
RTSJ - Real-time Specification for Java