Configuration for hard-realtime applications

For predictable execution of memory allocation, more care is needed when selecting memory related options. No dynamic heap size increments should be used if the break introduced by the heap size expansion can harm the realtime guarantees required by the application. Also, the heap size must be set such that the implied garbage collection work is tolerable.

The memory analyzer tool is used to determine the garbage collector settings during a runtime measurement. Together with the numblocks command, they permit an accurate prediction of the time required for each memory allocation. The following sections explain the required configuration of the system.

Usage of the Memory Analyzer tool

The Memory Analyzer is a tool for fine tuning an application's memory requirements and the realtime guarantees that can be given when allocating objects within Java code running on the Jamaica Virtual Machine.

The Memory Analyzer is integrated into the Builder tool. It can be activated by setting the command line option -analyze=<accuracy>.

Using the Memory Analyzer Tool is a three-step process: First, an application is built using the Memory Analyzer. The resulting executable file can then be executed to determine its memory requirements. Finally, the result of the execution can be used to fine tune the final version of the application.

Building using the Memory Analyzer

As an example, we will build the HelloWorld example application that was presented in the Section called Example in Chapter 6. This can be done by providing the option -analyze to the builder and giving the required accuracy of the analysis in percent. In this example, we use an accuracy of 5%:

  > jamaica -analyze=5 HelloWorld
      
Reading configuration from '/usr/local/jamaica/target/linux-x86/etc/
  jamaica.conf'...
Reading configuration from '/usr/local/jamaica/etc/jamaica.conf'...
Jamaica Builder Tool 3.4 Release 1
Generating code for target 'linux-x86', optimisation 'none'
 + HelloWorld__.c
 + HelloWorld__.h
Class file compaction gain: 82.33504% (7683905 ==> 1357359)
 * C compiling 'HelloWorld__.c'
 + HelloWorld__nc.o
 * linking
 * stripping
    
  

Measuring an application's memory requirements

The build process is performed exactly as it would be without the -analyze option, except that the garbage collector is told to measure the application's memory usage with the given accuracy. The result of this measurement is printed to the console after execution of the application:

  > ./HelloWorld
      
              Hello        World!
           Hello       World!
        Hello     World!
     Hello    World!
   Hello   World!
 Hello  World!
Hello World!
Hello World!
Hello World!
Hello World!
 Hello  World!
   Hello   World!
     Hello    World!
        Hello     World!
           Hello       World!
              Hello        World!
                 Hello         World!
...

### Recommended heap size: 1831K.
### Application used at most 1107552 bytes for reachable objects on
  the Java heap
### (accuracy 5%).
###
### Reserved memory is set to 10%.  To obtain lower memory bounds
### or worst-case GC overhead, set reserved memory to 0.
###
### Worst case allocation overhead:
###     heapSize        dynamic GC      const GC work
###     5723K           6               3
###     4397K           7               4
###     3655K           8               4
###     3191K           9               4
###     2869K           10              4
###     2459K           12              5
###     2212K           14              5
###     2041K           16              6
###     1922K           18              6
###     1831K           20              7
###     1704K           24              8
###     1620K           28              9
###     1561K           32              10
###     1515K           36              11
###     1480K           40              12
###     1429K           48              14
###     1394K           56              17
###     1370K           64              19
###     1311K           96              27
###     1283K           128             36
###     1255K           192             53
###     1242K           256             69
###     1230K           384             100
    
  

The output consists of the maximum heap memory demand plus a table of possible heap sizes and their allocation overheads. In this example, the application uses a maximum of 1107552 bytes of memory for the Java heap. The specified accuracy of 5% means that the actual memory usage of the application will be up to 5% less than the measured value, but not higher. JamaicaVM uses the Java heap to store all dynamic data structures internal to the virtual machine (as Java stacks, classes, etc.), which explains the relatively high memory demand for this small application.

Fine tuning the final executable application

In addition to printing the measured memory requirements of the application, in analyze mode Jamaica also prints a table of possible heap sizes and corresponding worst case allocation overheads. The worst case allocation overhead is given in units of garbage collection work that are needed to allocate one block of memory (typically 32 bytes). The amount of time in which these units of garbage collection work can be done is platform dependent. For example, on the PowerPC processor, a unit corresponds to the execution of about 160 machine instructions.

From this table, we can choose the minimum heap size that corresponds to the desired worst case execution time for the allocation of one block of memory. A heap size of 1831K corresponds to a worst case of 20 units of garbage collection work (3200 machine instructions on the PowerPC) per block allocation, while a smaller heap size of, for example, 1480K can only guarantee a worst case execution time of 40 units of garbage collection work (6400 PowerPC- instructions) per block allocation.

If we find that for our application 14 units of garbage collection work per allocation is sufficient to satisfy all realtime requirements, we can build the final application using a heap of 2212K:

  > jamaica -heapSize=2212K -maxHeapSize=2212K HelloWorld
      
Reading configuration from '/usr/local/jamaica/target/linux-x86/etc/
  jamaica.conf'...
Reading configuration from '/usr/local/jamaica/etc/jamaica.conf'...
Jamaica Builder Tool 3.4 Release 1
Generating code for target 'linux-x86', optimisation 'none'
 + HelloWorld__.c
 + HelloWorld__.h
Class file compaction gain: 82.33504% (7683905 ==> 1357359)
 * C compiling 'HelloWorld__.c'
 + HelloWorld__nc.o
 * linking
 * stripping
    
  

Note that both options, heapSize and maxHeapSize, are set to the same value. This creates an application that has the same initial heap size and maximum heap size, i.e., the heap size is not increased dynamically. This is required to ensure that the maximum of 14 units of garbage collection work per unit of allocation is respected during the whole execution of the application. With a dynamically growing heap size, an allocation that happens to require increasing the heap size will otherwise be blocked until the heap size is increased sufficiently.

The resulting application will now run with the minimum amount of memory that guarantees the selected worst case execution time for memory allocation. The actual amount of garbage collection work that is performed is determined dynamically depending on the current state of the application (including, for example, its memory usage) and will in most cases be significantly lower than the described worst case behavior, so that on average an allocation is significantly cheaper than the worst case allocation cost.

Constant Garbage Collection Work

For applications that require best worst case execution times, where average case execution time is less important, Jamaica also provides the option to statically select the amount of garbage collection work. This forces the given amount of garbage collection work to be performed at any allocation, without regard to the current state of the application. The advantage of this static mode is that worst case execution times are lower than using dynamic determination of garbage collection work. The disadvantage is that any allocation requires this worst case amount of garbage collection work.

The output generated using the option -analyze also shows possible values for the constant garbage collection option. In the example above, the amount of garbage collection work required varies from 3 to 100 units for heap sizes between 5723k and 1230k bytes. A unit of garbage collection work is the same as in the dynamic case, i.e., about 160 machine instructions on the PowerPC processor.

Similarly, if we want to give the same guarantee of 14 units of work for the worst case execution time of the allocation of a block of memory, a heap size of 1429k bytes is sufficient. To inform the builder that constant garbage collection work should be used, the option -constGCwork and the number of units of work should be specified when building the application:

  > jamaica -heapSize=1429K -maxHeapSize=1429K
 -constGCwork=14 HelloWorld
      
Reading configuration from '/usr/local/jamaica/etc/jamaica.conf'...
Jamaica Builder Tool 3.4 Release 1
Generating code for target 'linux-x86', optimisation 'none'
 + HelloWorld__.c
 + HelloWorld__.h
Class file compaction gain: 82.33504% (7683905 ==> 1357359)
 + HelloWorld__nc.o
 * C compiling 'HelloWorld__.c'
 * linking
 * stripping
    
  

Comparing dynamic mode and constant GC work mode

Which option you should choose (dynamic mode or constant garbage collection) depends strongly on the kind of application. If worst case execution time and low jitter are the most important criteria, constant garbage collection work will usually provide the better performance with smaller heap sizes. But if average case execution time is also an issue, dynamic mode will typically give better overall throughput, even though for equal heap sizes the guaranteed worst case execution time is longer with dynamic mode than with constant garbage collection work.

Gradual degradation may also be important. Dynamic mode and constant garbage collection work differ significantly when the application does not stay within the memory bounds that were fixed when the application was built.

There are a number of reasons an application might be using more memory:

Whatever the reason, it may be important in some environments to understand the behavior of memory management in the case the application exceeds the assumed heap usage.

In dynamic mode, the worst-case execution time for an allocation can no longer be guaranteed as soon as the application uses more memory. But as long as the excess heap used stays small, the worst-case execution time will increase only slightly. This means that the original worst-case execution time may not be exceeded at all or only by a small amount. However, the garbage collector will still work properly and recycle enough memory to keep the application running.

If the constant garbage collection work option is chosen, the amount of garbage-collection work will not increase even if the application uses more memory than originally anticipated. Allocations will still be made within the same worst-case execution time. Instead, the collector cannot give a guarantee that it will recycle memory fast enough. This means that the application may fail abruptly with an out-of-memory error. Static mode does not provide graceful degradation of performance in this case, but may cause abrupt failure even if the application exceeds the expected memory requirements only slightly.

Determination of the worst case execution time of an allocation

As we have just seen, the worst case execution time of an allocation depends on the amount of garbage collection work that has to be performed for the allocation. The configuration of the heap as shown above gives a worst case number of garbage collection work units that need to be performed for the allocation of one block of memory. In order to determine the actual time an allocation might take in the worst case, it is also necessary to know the number of blocks that will be allocated and the platform dependent worst case execution time of one unit of garbage collection work.

For an allocation statement S we get the following equation to calculate the worst case-execution time:

wcet(S) = numblocks(S) · max_gc_units · wcet_of_gc_unit

Where

Numblocks usage

An important value required to calculate the worst case execution time of an allocation is the number of blocks required to represent the allocated object. Jamaica provides the NumBlocks tool to determine this number of blocks. The following section describes the usage of this tool in detail.

A variety of arguments can be provided to control the NumBlocks tool. The arguments can be provided directly to numblocks, or using the property file numblocks.conf.

The syntax is as follows:

numblocks [-help (--help, -h, -?)] [-Xhelp] [-version]
          [-verbose] [-showSettings] [-saveSettings=<file>]
          [-configuration=<file>] [-all] [-classpath
          (-cp)=<path>] [-bootclasspath
          (-Xbootclasspath)=<path>] [-numThreads=<n>] 
          class1{"["<len>"]"} [... classn{"["<len>"]"}]

General

These are general options providing information about numblocks itself or enabling the use of script files that specify further options

-help (--help, -h, -?)

The help option displays the usage of the NumBlocks tool and a short description of all possible standard command line options

-Xhelp

The Xhelp option displays the usage of the NumBlocks tool and a short description of all possible standard and extended command line options. Extended command line options are not needed for normal control of the numblocks command. They are used to configure tools and options to provide tools required internally for JamaicaVM development.

-version

The version option prints the version of the Jamaica Numblocks Tool and exits.

-verbose

The verbose option sets the verbose level. If verbose level is greater than 0, additional output on the state of the build process is printed to standard out.

-showSettings

The currently used options of JamaicaVM Numblocks are written to stdout in property file format. To make these the default settings, copy these options into <JAMAICA_HOME>/etc/numblocks.conf.

-saveSettings=<file>

The currently used options of JamaicaVM Numblocks are written to stdout in property file format. To make these the default settings, copy these options into <JAMAICA_HOME>/etc/numblocks.conf.

-configuration=<file>

The set of options used to perform numblocks are read from the provided file. The format used to define options must be identical to the default configuration file <JAMAICA_HOME>/etc/numblocks.conf.

Classes, files, and paths

These options allow to specify classes and paths to be used by numblocks.

-all

If the all option is set, the number of blocks required for the allocation of objects of all classes in this application will be displayed.

-classpath (-cp)=<path>

The classpath option specifies the paths that are to be used to search for class files. A list of paths separated by the path separator char (`:' on Unix systems) can be specified. This list will be traversed from left to right when the Builder tries to load a class.

-bootclasspath (-Xbootclasspath)=<path>

The bootclasspath option specifies the default path used for loading system classes.

Memory and threads settings

-numThreads=<n>

The numThreads Builder option has an influence on the number of blocks required for some objects. Consequently, it must be provided to numblocks for all applications that are built with a specified number of threads.

Examples

Imagine that we want to determine the worst-case execution time (wcet) of an allocation of a StringBuffer object, as was done in the HelloWorld.java example shown above. If this example was built with the dynamic garbage collection option and a heap size of 443K bytes, we get

max_gc_units = 14

as has been shown above.

If our target platform gives a worst case execution time for one unit of garbage collection work of 1.6μs, we have

wcet_of_gc_unit = 1.6μs

We use the numblocks tool to find the number of blocks required for the allocation of a java.lang.StringBuffer object:

  > numblocks java.lang.StringBuffer
  1
  

A StringBuffer object requires just a single block of memory, so that

numblocks(new StringBuffer()) = 1

and the total worst case-execution time of the allocation becomes

wcet(new StringBuffer()) = 1 · 14 · 1.6μs = 22.4μs

If we had used the constant garbage collection option with the same heap size, the amount of garbage collection work on an allocation of one block could have been fixed at 6 units. In that case the worst-case-execution-time of the allocation becomes

wcetconstGCwork(new StringBuffer()) = 1 · 6 · 1.6μs = 9.6μs

After creation of the java.lang.StringBuffer object, a character array of 16 elements is allocated during the execution of StringBuffer's initialization routine. For this allocation, we can determine the worst-case-execution-time by first determining the number of blocks required:

  > numblocks "char[16]"
  2
  

and we get

wcet(new char[16]) = 2 · 14 · 1.6μs = 44.8μs

and

wcetconstGCwork(new char[16]) = 2 · 6 · 1.6μs = 19.2μs

Here are some typical values for the number of blocks required for the allocation of different objects or arrays:

Table 7-1. Typical number of blocks for objects

Classnumblocks
new java.util.Vector()1
new boolean[1024]5
new byte[64]3
new char[256]19
new int[1024]147
new Object[1000000]142860

The output of numblocks when using the -all option can be used to get a quick overview on the number of blocks for all classes used by an application:

  > numblocks -all HelloWorld
      
Class:                                          Blocks:
HelloWorld                                       1
com/aicas/jamaica/AccessCheck                    1
com/aicas/jamaica/Archive                        1
com/aicas/jamaica/Archive$PathVisitor            1
com/aicas/jamaica/Archive$Visitor                1
com/aicas/jamaica/ArchiveDirectory               1
com/aicas/jamaica/ArchiveEntry                   1
com/aicas/jamaica/BuilderError                   2
com/aicas/jamaica/DirectoryEntry                 2
com/aicas/jamaica/NYIException                   2
com/aicas/jamaica/lang/CpuTime                   1
com/aicas/jamaica/lang/Debug                     1
com/aicas/jamaica/lang/LowLevelRTSJ              1
com/aicas/jamaica/lang/Process                   1
com/aicas/jamaica/lang/Profile                   1
com/aicas/jamaica/lang/Profile$1                 5
com/aicas/jamaica/lang/Profile$2                 4
com/aicas/jamaica/lang/Profile$3                 1
com/aicas/jamaica/lang/Profile$Count             1
com/aicas/jamaica/lang/Wait                      1
com/aicas/jamaica/util/False                     1
com/aicas/java/net/protocol/rom/Handler          1
com/aicas/java/net/protocol/rom/RomURLCnctn      1
...